diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2007-03-30 02:51:36 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2007-03-30 02:51:36 +0000 |
commit | 97e432df4e7ffae5ae89939539172d5923a46014 (patch) | |
tree | 7890f85ba00bbd9a15488a54278ba250f41946ec | |
parent | 64dd4a03dedeea0f0240951d8ec73cd519fb3900 (diff) | |
download | netpbm-mirror-97e432df4e7ffae5ae89939539172d5923a46014.tar.gz netpbm-mirror-97e432df4e7ffae5ae89939539172d5923a46014.tar.xz netpbm-mirror-97e432df4e7ffae5ae89939539172d5923a46014.zip |
Move Advanced series to 10.38
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@267 9d0c8265-081b-0410-96cb-a4ca84ce46f8
35 files changed, 3353 insertions, 1498 deletions
diff --git a/GNUmakefile b/GNUmakefile index 0f5c04fd..a65ac0d4 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -174,7 +174,7 @@ 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. +# that means either the user skipped 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 " diff --git a/Makefile.version b/Makefile.version index e0fd217a..5099c5fe 100644 --- a/Makefile.version +++ b/Makefile.version @@ -1,4 +1,4 @@ NETPBM_MAJOR_RELEASE = 10 -NETPBM_MINOR_RELEASE = 37 -NETPBM_POINT_RELEASE = 06 +NETPBM_MINOR_RELEASE = 38 +NETPBM_POINT_RELEASE = 0 diff --git a/buildtools/Makefile.manpage b/buildtools/Makefile.manpage index e8c17972..b25bc19e 100644 --- a/buildtools/Makefile.manpage +++ b/buildtools/Makefile.manpage @@ -4,7 +4,7 @@ MAKEMAN = makeman -MANDIR = /usr/share/man/man1 +MANDIR = /usr/share/man/ # These can convert to man pages cleanly MAN1 = \ @@ -16,6 +16,7 @@ MAN1 = \ bmptopnm.1 \ bmptoppm.1 \ brushtopbm.1 \ + cameratopam.1 \ cmuwmtopbm.1 \ ddbugtopbm.1 \ escp2topbm.1 \ @@ -41,6 +42,7 @@ MAN1 = \ leaftoppm.1 \ lispmtopgm.1 \ macptopbm.1 \ + manweb.1 \ mdatopbm.1 \ mgrtopbm.1 \ mrf.1 \ @@ -58,6 +60,7 @@ MAN1 = \ pamedge.1 \ pamendian.1 \ pamfile.1 \ + pamfixtrunc.1 \ pamflip.1 \ pamfunc.1 \ pamgauss.1 \ @@ -74,8 +77,8 @@ MAN1 = \ pamstereogram.1 \ pamstretch-gen.1 \ pamstretch.1 \ - pamsummcol.1 \ pamsumm.1 \ + pamsummcol.1 \ pamtodjvurle.1 \ pamtohdiff.1 \ pamtohtmltbl.1 \ @@ -186,8 +189,8 @@ MAN1 = \ pnmquant.1 \ pnmremap.1 \ pnmrotate.1 \ - pnmscalefixed.1 \ pnmscale.1 \ + pnmscalefixed.1 \ pnmshear.1 \ pnmsmooth.1 \ pnmsplit.1 \ @@ -208,8 +211,8 @@ MAN1 = \ pnmtorle.1 \ pnmtosgi.1 \ pnmtosir.1 \ - pnmtotiffcmyk.1 \ pnmtotiff.1 \ + pnmtotiffcmyk.1 \ pnmtoxwd.1 \ ppm3d.1 \ ppmbrighten.1 \ @@ -230,8 +233,8 @@ MAN1 = \ ppmnorm.1 \ ppmntsc.1 \ ppmpat.1 \ - ppmquantall.1 \ ppmquant.1 \ + ppmquantall.1 \ ppmrainbow.1 \ ppmrelief.1 \ ppmrough.1 \ @@ -243,12 +246,12 @@ MAN1 = \ ppmtoarbtxt.1 \ ppmtobmp.1 \ ppmtoeyuv.1 \ - ppmtogif.1 \ ppmtoicr.1 \ ppmtoilbm.1 \ ppmtojpeg.1 \ ppmtoleaf.1 \ ppmtolj.1 \ + ppmtomap.1 \ ppmtomitsu.1 \ ppmtompeg.1 \ ppmtoneo.1 \ @@ -301,6 +304,35 @@ MAN1 = \ yuvsplittoppm.1 \ yuvtoppm.1 \ zeisstopnm.1 \ + pamaddnoise.1 \ + pambackground.1 \ + pambayer.1 \ + pamdepth.1 \ + pamenlarge.1 \ + pamgradient.1 \ + pammasksharpen.1 \ + pammixinterlace.1 \ + pampick.1 \ + pamrgbatopng.1 \ + pamsplit.1 \ + pamthreshold.1 \ + pamtilt.1 \ + pamtofits.1 \ + pamtogif.1 \ + pamtosvg.1 \ + pamtotiff.1 \ + pamtoxvmini.1 \ + pamx.1 \ + pbmtoibm23xx.1 \ + pbmtomatrixorbital.1 \ + pgmdeshadow.1 \ + pgmmake.1 \ + pgmmedian.1 \ + ppmdcfont.1 \ + ppmddumpfont.1 \ + ppmdmkfont.1 \ + ppmdraw.1 \ + rlatopam.1 \ MAN3 = \ libnetpbm.3 \ @@ -318,39 +350,57 @@ MAN5 = \ extendedopacity.5 \ pam.5 \ pbm.5 \ + pfm.5 \ pgm.5 \ pnm.5 \ ppm.5 \ +# These things do get converted to man pages and installed. MANPAGES = $(MAN1) netpbm.1 $(MAN3) $(MAN5) HTMLMANUALS = $(MAN1:.1=.html) $(MAN3:.3=.html) $(MAN5:.5=.html) + +# These things don't get converted to manual pages. +EXCEPTIONS = directory.html libnetpbm_dir.html libnetpbm_draw.html error.html +STUBS = pcdindex.1 ppmcolors.1 pnmflip.1 ppmtogif.1 + +# This works if you've done a full SVN checkout. +USERGUIDE= ../../userguide + 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 +# List everything in the userguide directory that is not categorized above. +# Use this to check that 'make manpages' converts as much as possible +# of the HTML documentation. +uncategorized: + @echo $(HTMLMANUALS) $(EXCEPTIONS) $(STUBS) | tr " " "\n" | sort >LIST1 + @(cd $(USERGUIDE); ls | sort) >LIST2 + @comm -3 LIST1 LIST2 + @rm LIST1 LIST2 -# Make man pages -- reports bad lines to standard error +# Make man pages -- reports bad lines to standard error. manpages: - @python $(MAKEMAN) index.html $(HTMLMANUALS) - mv index.1 netpbm.1 + @python $(MAKEMAN) -d $(USERGUIDE) 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 + @for x in $(MANPAGES); do doclifter -v $$x; done + @for x in $(MANPAGES); do xmllint -xinclude --postvalid $$x.xml >/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 +installman: + set -x + 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 + +# This will uninstall them +uninstallman: + for f in $(MAN1); do rm -f $(MANDIR)/man1/$$f.gz; fi; done + for f in $(MAN3); do rm -f $(MANDIR)/man3/$$f.gz; fi; done + for f in $(MAN5); do rm -f $(MANDIR)/man5/$$f.gz; fi; done + +oldclean: # Clean up old locations on Fedora Core 2 rm -f $(MANDIR)/man1/extendedopacity.1.gz rm -f $(MANDIR)/man3/directory.3.gz diff --git a/buildtools/configure.pl b/buildtools/configure.pl index d0ff36b9..cb1723eb 100755 --- a/buildtools/configure.pl +++ b/buildtools/configure.pl @@ -1077,27 +1077,27 @@ sub getLinuxsvgaLibrary($@) { my ($svgalib, $svgalibhdr_dir); if ($platform eq "GNU") { - { - my $default; + 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"); + if (-d('/usr/link/svgalib')) { + $default = '/usr/link/svgalib/libvga.so'; + } elsif (-d('/usr/lib/svgalib')) { + $default = '/usr/lib/svgalib/libvga.so'; + } elsif (system('ldconfig -p | grep libvga &>/dev/null')) { + $default = 'libvga.so'; + } else { + $default = 'none'; + } - my $response = prompt("library filename or 'none'", $default); + print("What is your Svgalib library?\n"); + + my $response = prompt("library filename or 'none'", $default); - if ($response ne "none") { - $svgalib = $response; - } + if ($response ne 'none') { + $svgalib = $response; } } - if (defined($svgalib)) { + if (defined($svgalib) && $svgalib ne 'none') { my $default; if (-d('/usr/include/svgalib')) { @@ -2150,13 +2150,11 @@ 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("We have created the file 'Makefile.config'. You may want to look \n"); +print("at it and edit it to your requirements and taste before doing the \n"); +print("make.\n"); print("\n"); - print("Now you may proceed with 'make'\n"); print("\n"); diff --git a/buildtools/makeman b/buildtools/makeman index 634a2c79..2e122779 100755 --- a/buildtools/makeman +++ b/buildtools/makeman @@ -19,7 +19,8 @@ # * 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. +# You can use the <?makeman ?> PI to pass text directly through to the +# generated manual page, A major use is to insert format lines for tables. # # By Eric S. Raymond <esr@thyrsus.com> # Version 1.0, July 26 2004 @@ -29,7 +30,7 @@ import os, sys, exceptions, re source = "netpbm documentation" section = 1 -warning = '''\ +warning = r'''\ .\" 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 @@ -47,11 +48,15 @@ def makeman(name, file, indoc): # Dot at left margin confuses troff. # This program generates these, indoc = indoc.replace("\n.", "\n@%@%@") + # Protect escapes before we try generating font changes. + indoc = indoc.replace("\\", r"\e") # Header-bashing + indoc = indoc.replace('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "DTD/xhtml11.dtd">', "") + indoc = indoc.replace('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">', "") 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('<meta http-equiv="Content-Type" content="text/html; charset=us-ascii"/>', "") 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>", "") @@ -93,8 +98,8 @@ def makeman(name, file, 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) + indoc = re.sub("(?i)\n *<BLOCKQUOTE>", "\n.RS", indoc) + indoc = re.sub("(?i)\n *</BLOCKQUOTE>", "\n.RE", indoc) # Highlight processing indoc = re.sub("(?i)<B>", r"\\fB", indoc) indoc = re.sub("(?i)</B>", r"\\fP", indoc) @@ -108,6 +113,8 @@ def makeman(name, file, 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)<CODE>", r"\\f(CW", indoc) + indoc = re.sub("(?i)</CODE>", 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) @@ -150,6 +157,7 @@ def makeman(name, file, indoc): indoc = indoc.replace("<", "@#!#@").replace(">", "#@!@#").replace("&", "#!@!@!#") indoc = indoc.replace("×", r"\(mu") indoc = indoc.replace("®", r"\*R") + indoc = indoc.replace("©", r"\(co") # Turn anchors into .UN tags indoc = re.sub('(?i)<A NAME *= *"#?([a-zA-Z][a-zA-Z0-9.-]+)">(?: )*</A>\s*', ".UN \\1\n", indoc) # Strip off the index trailer @@ -159,17 +167,17 @@ def makeman(name, file, indoc): 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>', + indoc = re.sub('(?i)<H2><A (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</A></H2>', ".UN \\1\n.SH \\2", indoc) - indoc = re.sub('(?i)<H3><A (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</A></H3>', + indoc = re.sub('(?i)<H3><A (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</A></H3>', ".UN \\1\n.SS \\2", indoc) - indoc = re.sub('(?i)<H4><A (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</A></H4>', + indoc = re.sub('(?i)<H4><A (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</A></H4>', ".UN \\1\n.B \\2", indoc) - indoc = re.sub('(?i)<H2 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H2>', + indoc = re.sub('(?i)<H2 (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</H2>', ".UN \\1\n.SH \\2", indoc) - indoc = re.sub('(?i)<H3 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H3>', + indoc = re.sub('(?i)<H3 (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</H3>', ".UN \\1\n.SS \\2", indoc) - indoc = re.sub('(?i)<H4 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H4>', + indoc = re.sub('(?i)<H4 (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</H4>', ".UN \\1\n.B \\2", indoc) # Sections without IDs indoc = re.sub('(?i)<H2>([^><]*)</H2>', ".SH \\1", indoc) @@ -192,7 +200,7 @@ def makeman(name, file, indoc): # Passthrough indoc = re.sub(r"<\?makeman (.*) \?>", r'\1', indoc) # Comments - indoc = re.sub("<!--([^-])*-->", r'.\"\1', indoc) + indoc = re.sub("<!--([^\n])*-->", r'.\"\1', indoc) # Image tags indoc = re.sub(' *<img src="([^"]*)" alt="([^"]*)"( *[a-z]*="?[0-9]*"?)*>', ".B \\2\n.IMG -C \\1", indoc) # Special characters @@ -217,7 +225,7 @@ def makeman(name, file, indoc): # Time for error checking now badlines = [] for line in indoc.split("\n"): - if "<" in line or ">" in line or re.search("&.*;", line): + if "<" in line or ">" in line.replace(" >", "") or re.search("&.*;", line): badlines.append(line) if badlines: sys.stderr.write(("Bad lines from %s:\n-----------------\n" % file) + "\n".join(badlines) + "\n-----------------\n") @@ -236,19 +244,22 @@ def makeman(name, file, indoc): def main(args, mainout=sys.stdout, mainerr=sys.stderr): global sectmap import getopt - (options, arguments) = getopt.getopt(args, "v") + (options, arguments) = getopt.getopt(args, "vd:") + dirprefix = "" verbosity = 0 for (switch, val) in options: - if switch == '-v': + if switch == '-d': # Set HTML input directory + dirprefix = val + elif switch == '-v': # Enable verbose error reporting verbosity += 1 try: # First pass: gather locations for crossreferences: sectmap = {} for file in arguments: try: - infp = open(file) + infp = open(os.path.join(dirprefix, file)) except: - sys.stderr.write("can't open %s" % name) + sys.stderr.write("makeman: can't open %s\n" % file) continue indoc = infp.read() infp.close() @@ -277,9 +288,9 @@ def main(args, mainout=sys.stdout, mainerr=sys.stderr): # Second pass: do formatting for file in arguments: try: - infp = open(file) + infp = open(os.path.join(dirprefix, file)) except: - sys.stderr.write("can't open %s" % name) + sys.stderr.write("makeman: can't open %s\n" % file) continue indoc = infp.read() infp.close() @@ -321,6 +332,7 @@ def main(args, mainout=sys.stdout, mainerr=sys.stderr): (exc_type, exc_value, exc_traceback) = sys.exc_info() raise exc_type, exc_value, exc_traceback else: + mainerr.write("makeman: internal error!\n") return 5 if __name__ == "__main__": diff --git a/converter/other/bmepsoe.c b/converter/other/bmepsoe.c index cdb52779..1df687f9 100644 --- a/converter/other/bmepsoe.c +++ b/converter/other/bmepsoe.c @@ -209,76 +209,85 @@ static void after_flate_add(Output_Encoder *o, int 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; + + +static void +do_flate_flush(Output_Encoder * const oP, + int const final) { + + Bytef *iptr, *optr, *xptr; + unsigned long is; + unsigned long os; + unsigned long xs; + int err; + int mustContinue; + + iptr = oP->fl_i_buffer; optr = oP->fl_o_buffer; + is = oP->fl_i_size; os = oP->fl_o_size; + + if (iptr && optr && is && os) { + is = oP->fl_i_used; + if (is) { + oP->flate_stream.next_in = iptr; + oP->flate_stream.avail_in = is; + if (final) { + mustContinue = 1; + while (mustContinue) { + oP->flate_stream.next_out = optr; + oP->flate_stream.avail_out = os; + mustContinue = 0; + err = deflate(&oP->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 - (oP->flate_stream.avail_out); + while (xs--) { + after_flate_add(oP, *(xptr++)); + } + } break; + case Z_OK : { + mustContinue = 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; - } + xs = os - (oP->flate_stream.avail_out); + while (xs--) { + after_flate_add(oP, *(xptr++)); + } + } break; + default : { + } break; + } + } + } else { + mustContinue = 1; + while (mustContinue) { + mustContinue = 0; + oP->flate_stream.avail_out = os; + oP->flate_stream.next_out = optr; + err = deflate(&oP->flate_stream, 0); + switch (err) { + case Z_OK: { + if (oP->flate_stream.avail_in) { + mustContinue = 1; + } - xptr = optr; xs = os - ((o->flate_stream).avail_out); - while(xs--) { - after_flate_add(o, (*(xptr++))); - } - } break; - default : { - } break; - } - } - } + xptr = optr; xs = os - (oP->flate_stream.avail_out); + while (xs--) { + after_flate_add(oP, *(xptr++)); + } + } break; + default : { + } break; + } + } + } + } } - } - } + + static void flate_add(Output_Encoder *o, int b) { Byte bt; @@ -457,17 +466,20 @@ static void rl_flush(Output_Encoder *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_add(Output_Encoder * const oP, + int const b) { + if (oP->mode & OE_RL) + rl_add(oP, b); + else + after_rl_add(oP, b); } + + static void internal_byte_flush(Output_Encoder *o) { @@ -479,19 +491,23 @@ static void internal_byte_flush(Output_Encoder *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_add(Output_Encoder * const oP, + int const b) { + oP->bit_value = 2 * oP->bit_value + (b ? 1 : 0); + oP->bit_consumed = oP->bit_consumed + 1; + if (oP->bit_consumed >= 8) { + oP->bit_consumed = 0; + internal_byte_add(oP, oP->bit_value); + oP->bit_value = 0; + } } + + void oe_bit_flush(Output_Encoder *o) { @@ -511,26 +527,31 @@ void oe_bit_flush(Output_Encoder *o) } -void oe_byte_add(Output_Encoder *o, int b) -{ + + +void +oe_byte_add(Output_Encoder * const oP, + int const 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; + if (oP->bit_consumed) { + int testval; + int i; + testval = 128; + for (i = 0; i < 8; ++i) { + if (b & testval) { + oe_bit_add(oP, 1); + } else { + oe_bit_add(oP, 0); + } + testval = testval / 2; + } + } else { + internal_byte_add(oP, b); } - } else { - internal_byte_add(o,b); - } - } + + void oe_byte_flush(Output_Encoder *o) { diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c index d7d54775..0e261da4 100644 --- a/converter/other/giftopnm.c +++ b/converter/other/giftopnm.c @@ -23,10 +23,10 @@ #include <string.h> #include <assert.h> -#include "pnm.h" -#include "shhopt.h" #include "mallocvar.h" #include "nstring.h" +#include "shhopt.h" +#include "pnm.h" #define GIFMAXVAL 255 #define MAXCOLORMAPSIZE 256 @@ -54,6 +54,32 @@ ReadOK(FILE * const fileP, } + +static void +readFile(FILE * const ifP, + unsigned char * const buffer, + size_t const len, + const char ** const errorP) { + + size_t bytesRead; + + bytesRead = fread(buffer, len, 1, ifP); + + if (bytesRead == len) + *errorP = NULL; + else { + if (ferror(ifP)) + asprintfN(errorP, "Error reading file. errno=%d (%s)", + errno, strerror(errno)); + else if (feof(ifP)) + asprintfN(errorP, "End of file encountered"); + else + asprintfN(errorP, "Short read -- %u bytes of %u", bytesRead, len); + } +} + + + #define LM_to_uint(a,b) (((b)<<8)|(a)) static int const maxnum_lzwCode = (1<<MAX_LZW_BITS); @@ -72,6 +98,7 @@ struct cmdlineInfo { */ const char * alpha_filename; unsigned int quitearly; + unsigned int repair; }; @@ -101,8 +128,10 @@ parseCommandLine(int argc, char ** argv, &cmdlineP->verbose, 0); OPTENT3(0, "comments", OPT_FLAG, NULL, &cmdlineP->comments, 0); - OPTENT3(0, "quitearly", OPT_FLAG, NULL, + OPTENT3(0, "quitearly", OPT_FLAG, NULL, &cmdlineP->quitearly, 0); + OPTENT3(0, "repair", OPT_FLAG, NULL, + &cmdlineP->repair, 0); OPTENT3(0, "image", OPT_STRING, &image, &imageSpec, 0); OPTENT3(0, "alphaout", OPT_STRING, &cmdlineP->alpha_filename, @@ -232,13 +261,15 @@ readColorMap(FILE *ifP, const int colormapsize, static bool zeroDataBlock = FALSE; /* the most recently read DataBlock was an EOD marker, i.e. had - zero length */ + zero length + */ static void getDataBlock(FILE * const ifP, unsigned char * const buf, bool * const eofP, - unsigned int * const lengthP) { + unsigned int * const lengthP, + const char ** const errorP) { /*---------------------------------------------------------------------------- Read a DataBlock from file 'ifP', return it at 'buf'. @@ -258,10 +289,11 @@ getDataBlock(FILE * const ifP, unsigned char count; bool successfulRead; - long const pos=ftell(ifP); + long const pos = ftell(ifP); successfulRead = ReadOK(ifP, &count, 1); if (!successfulRead) { pm_message("EOF or error in reading DataBlock size from file" ); + *errorP = FALSE; *eofP = TRUE; *lengthP = 0; } else { @@ -270,17 +302,21 @@ getDataBlock(FILE * const ifP, *eofP = FALSE; *lengthP = count; - if (count == 0) + if (count == 0) { + *errorP = NULL; zeroDataBlock = TRUE; - else { + } 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); + + if (successfulRead) + *errorP = NULL; + else + asprintfN(errorP, + "EOF or error reading data portion of %u byte " + "DataBlock from file", count); } } } @@ -303,14 +339,15 @@ readThroughEod(FILE * const ifP) { while (!eod) { bool eof; unsigned int count; + const char * error; - getDataBlock(ifP, buf, &eof, &count); - if (eof) + getDataBlock(ifP, buf, &eof, &count, &error); + if (error || 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) + if (error || eof || count == 0) eod = TRUE; } } @@ -334,7 +371,11 @@ doCommentExtension(FILE * const ifP) { done = FALSE; while (!done) { bool eof; - getDataBlock(ifP, (unsigned char*) buf, &eof, &blocklen); + const char * error; + getDataBlock(ifP, (unsigned char*) buf, &eof, &blocklen, &error); + if (error) + pm_error("Error reading a data block in a comment extension. %s", + error); if (blocklen == 0 || eof) done = TRUE; else { @@ -355,8 +396,11 @@ doGraphicControlExtension(FILE * const ifP, bool eof; unsigned int length; static unsigned char buf[256]; + const char * error; - getDataBlock(ifP, buf, &eof, &length); + getDataBlock(ifP, buf, &eof, &length, &error); + if (error) + pm_error("Error reading 1st data block of Graphic Control Extension"); if (eof) pm_error("EOF/error encountered reading " "1st DataBlock of Graphic Control Extension."); @@ -451,28 +495,16 @@ struct getCodeState { */ bool streamExhausted; /* The last time we read from the input stream, we got an EOD marker - or EOF + or EOF or an error that prevents further reading of the stream. */ }; 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) { +getAnotherBlock(FILE * const ifP, + struct getCodeState * const gsP, + const char ** const errorP) { unsigned int count; unsigned int assumed_count; @@ -490,91 +522,104 @@ getAnotherBlock(FILE * const ifP, 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); + getDataBlock(ifP, &gsP->buf[gsP->bufCount], &eof, &count, errorP); + if (*errorP) + gsP->streamExhausted = TRUE; + else { + 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->bufCount += assumed_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); +static struct getCodeState getCodeState; - 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 void +getCode_init(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 int -getCode(FILE * const ifP, - int const codeSize, - bool const init) -{ +static void +getCode_get(struct getCodeState * const gsP, + FILE * const ifP, + int const codeSize, + bool * const eofP, + unsigned int * const codeP, + const char ** const errorP) { /*---------------------------------------------------------------------------- - If 'init', initialize the code getter. + Read and return the next lzw code from the file *ifP. + + 'codeSize' is the number of bits in the code we are to get. - Otherwise, read and return the next lzw code from the file *ifP. + Return *eofP == TRUE iff we hit the end of the stream. That means a legal + end of stream, marked by an EOD marker, not just end of file. An end of + file in the middle of the GIF stream is an error. - 'codeSize' is the number of bits in the code we are to get. + If there are bits left in the stream, but not 'codeSize' of them, we + call that a success with *eofP == TRUE. - Return -1 instead of a code if we encounter the end of the file. + Return the code read (assuming *eofP == FALSE and *errorP == NULL) + as *codeP. -----------------------------------------------------------------------------*/ - static struct getCodeState getCodeState; + 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, errorP); + else + *errorP = NULL; - int retval; + if (!*errorP) { + if ((gsP->curbit+codeSize) > gsP->bufCount*8) { + /* The buffer still doesn't have enough bits in it; that means + there were no data blocks left to read. + */ + *eofP = TRUE; + + { + 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; + unsigned 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; + *eofP = FALSE; + *codeP = code; + } + } +} - if (init) { - initGetCode(&getCodeState); - retval = 0; - } else - doGetCode(ifP, codeSize, &getCodeState, &retval); - return retval; -} struct stack { @@ -633,6 +678,7 @@ termStack(struct stack * const stackP) { stackP->stack = NULL; } + /*---------------------------------------------------------------------------- Some notes on LZW. @@ -689,7 +735,7 @@ struct decompressor { */ int next_tableSlot; /* Index in the code translation table of the next free entry */ - int firstcode; + unsigned 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 */ @@ -754,7 +800,7 @@ lzwInit(struct decompressor * const decompP, } resetDecompressor(decompP); - getCode(decompP->ifP, 0, TRUE); + getCode_init(&getCodeState); decompP->fresh = TRUE; @@ -774,7 +820,7 @@ lzwTerm(struct decompressor * const decompP) { static void expandCodeOntoStack(struct decompressor * const decompP, int const incode, - bool * const errorP) { + const char ** 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. @@ -785,12 +831,13 @@ expandCodeOntoStack(struct decompressor * const decompP, 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. + from which it was built is invalid), fail (return text explanation + as *errorP). -----------------------------------------------------------------------------*/ int code; - bool error; + const char * error; - error = FALSE; + error = NULL; /* Initial value */ if (incode < decompP->next_tableSlot) code = incode; @@ -812,8 +859,8 @@ expandCodeOntoStack(struct decompressor * const decompP, while (code > decompP->max_dataVal && !error) { if (stringCount > maxnum_lzwCode) { - pm_message("Error in GIF image: contains LZW string loop"); - error = TRUE; + asprintfN(&error, + "Error in GIF image: contains LZW string loop"); } else { ++stringCount; pushStack(&decompP->stack, decompP->table[1][code]); @@ -842,78 +889,105 @@ expandCodeOntoStack(struct decompressor * const decompP, } } - decompP->prevcode = incode; *errorP = error; + + decompP->prevcode = incode; } -static int -lzwReadByte(struct decompressor * const decompP) { +static void +lzwReadByteFresh(struct getCodeState * const getCodeStateP, + struct decompressor * const decompP, + bool * const endOfImageP, + unsigned int * const dataReadP, + const char ** const errorP) { + + /* 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. + */ + bool eof; + do { + getCode_get(getCodeStateP, decompP->ifP, decompP->codeSize, + &eof, &decompP->firstcode, errorP); + decompP->prevcode = decompP->firstcode; + } while (decompP->firstcode == decompP->clear_code && !*errorP && !eof); + + if (!*errorP) { + if (eof) + *endOfImageP = TRUE; + else if (decompP->firstcode == decompP->end_code) { + if (!zeroDataBlock) + readThroughEod(decompP->ifP); + *endOfImageP = TRUE; + } else { + *endOfImageP = FALSE; + *dataReadP = decompP->firstcode; + } + } +} + + + +static void +lzwReadByte(struct decompressor * const decompP, + unsigned int * const dataReadP, + bool * const endOfImageP, + const char ** const errorP) { /*---------------------------------------------------------------------------- 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: + We read and return the next byte of the decompressed image. - 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)). + If we can't, because the stream is too corrupted to make sense out of + it or the stream ends, we fail (return text description of why as + *errorP). - 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). + We forgive the case that the "end" code is the end of the stream -- + not followed by an EOD marker (zero length DataBlock). - Return -3 if we encounter errors in the LZW stream. + Iff we can't read a byte because we've hit the end of the image, + we return *endOfImageP = true. -----------------------------------------------------------------------------*/ - int retval; - - if (!stackIsEmpty(&decompP->stack)) - retval = popStack(&decompP->stack); - else if (decompP->fresh) { + if (!stackIsEmpty(&decompP->stack)) { + *errorP = NULL; + *endOfImageP = FALSE; + *dataReadP = 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; + + lzwReadByteFresh(&getCodeState, decompP, endOfImageP, dataReadP, + errorP); } 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; + unsigned int code; + bool eof; + getCode_get(&getCodeState, decompP->ifP, decompP->codeSize, + &eof, &code, errorP); + if (!*errorP) { + if (eof) + asprintfN(errorP, + "Premature end of file; no proper GIF closing"); + else { + if (code == decompP->clear_code) { + resetDecompressor(decompP); + lzwReadByte(decompP, dataReadP, endOfImageP, errorP); } else { - bool error; - expandCodeOntoStack(decompP, code, &error); - if (error) - retval = -3; - else - retval = popStack(&decompP->stack); + if (code == decompP->end_code) { + if (!zeroDataBlock) + readThroughEod(decompP->ifP); + *endOfImageP = TRUE; + *errorP = NULL; + } else { + expandCodeOntoStack(decompP, code, errorP); + if (!*errorP) + *dataReadP = popStack(&decompP->stack); + } } } } } - return retval; } @@ -1022,6 +1096,124 @@ addPixelToRaster(unsigned int const cmapIndex, static void +verifyPixelRead(bool const endOfImage, + const char * const readError, + unsigned int const cols, + unsigned int const rows, + unsigned int const failedRowNum, + const char ** const errorP) { + + if (readError) + *errorP = strdup(readError); + else { + if (endOfImage) + asprintfN(errorP, + "Error in GIF image: Not enough raster data to fill " + "%u x %u dimensions. Ran out of raster data in " + "row %u. The image has proper ending sequence, so " + "this is not just a truncated file.", + cols, rows, failedRowNum); + else + *errorP = NULL; + } +} + + + +static void +readRaster(struct decompressor * const decompP, + 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, + bool const tolerateBadInput) { + + struct pnmBuffer pnmBuffer; + enum pass pass; + bool fillingMissingPixels; + + pass = MULT8PLUS0; + pnmBuffer.xels = xels; + pnmBuffer.col = 0; + pnmBuffer.row = 0; + fillingMissingPixels = false; /* initial value */ + + while (pnmBuffer.row < rows) { + unsigned int colorIndex; + + if (fillingMissingPixels) + colorIndex = 0; + else { + const char * error; + + const char * readError; + unsigned int readColorIndex; + bool endOfImage; + + lzwReadByte(decompP, &readColorIndex, &endOfImage, &readError); + + verifyPixelRead(endOfImage, readError, cols, rows, pnmBuffer.row, + &error); + + if (readError) + strfree(readError); + + if (error) { + if (tolerateBadInput) { + pm_message("WARNING: %s. " + "Filling bottom %u rows with arbitrary color", + error, rows - pnmBuffer.row); + fillingMissingPixels = true; + } else + pm_error("Unable to read input image. %s. Use the " + "-repair option to try to salvage some of " + "the image", + error); + + colorIndex = 0; + } else + colorIndex = readColorIndex; + } + addPixelToRaster(colorIndex, &pnmBuffer, cols, rows, cmap, cmapSize, + interlace, transparentIndex, alphabits, &pass); + } +} + + + +static void +skipExtraneousData(struct decompressor * const decompP) { + + unsigned int byteRead; + bool endOfImage; + const char * error; + + lzwReadByte(decompP, &byteRead, &endOfImage, &error); + + if (error) + strfree(error); + else if (!endOfImage) { + pm_message("Extraneous data at end of image. " + "Skipped to end of image"); + + while (!endOfImage && !error) + lzwReadByte(decompP, &byteRead, &endOfImage, &error); + + if (error) { + pm_message("Error encountered skipping to end of image: %s", + error); + strfree(error); + } + } +} + + + +static void readImageData(FILE * const ifP, xel ** const xels, unsigned int const cols, @@ -1030,20 +1222,13 @@ readImageData(FILE * const ifP, unsigned int const cmapSize, bool const interlace, int const transparentIndex, - bit ** const alphabits) { + bit ** const alphabits, + bool const tolerateBadInput) { 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) " @@ -1057,29 +1242,10 @@ readImageData(FILE * const ifP, lzwInit(&decomp, ifP, lzwMinCodeSize); - while (pnmBuffer.row < rows) { - int const rc = lzwReadByte(&decomp); + readRaster(&decomp, xels, cols, rows, cmap, cmapSize, interlace, + transparentIndex, alphabits, tolerateBadInput); - 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"); + skipExtraneousData(&decomp); lzwTerm(&decomp); } @@ -1087,33 +1253,36 @@ readImageData(FILE * const ifP, static void -writePnm(FILE *outfile, xel ** const xels, - const int cols, const int rows, - const int hasGray, const int hasColor) { +writePnm(FILE * const outfileP, + xel ** const xels, + int const cols, + int const rows, + int const hasGray, + int const hasColor) { /*---------------------------------------------------------------------------- - Write a PNM image to the current position of file 'outfile' with + Write a PNM image to the current position of file *outfileP 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; + const char * formatName; if (hasColor) { format = PPM_FORMAT; - format_name = "PPM"; + formatName = "PPM"; } else if (hasGray) { format = PGM_FORMAT; - format_name = "PGM"; + formatName = "PGM"; } else { format = PBM_FORMAT; - format_name = "PBM"; + formatName = "PBM"; } if (verbose) - pm_message("writing a %s file", format_name); + pm_message("writing a %s file", formatName); - if (outfile) - pnm_writepnm(outfile, xels, cols, rows, + if (outfileP) + pnm_writepnm(outfileP, xels, cols, rows, (xelval) GIFMAXVAL, format, FALSE); } @@ -1218,7 +1387,8 @@ readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) { static void readExtensions(FILE* const ifP, struct gif89 * const gif89P, - bool * const eodP) { + bool * const eodP, + const char ** const errorP) { /*---------------------------------------------------------------------------- Read extension blocks from the GIF stream to which the file *ifP is positioned. Read up through the image separator that begins the @@ -1227,33 +1397,50 @@ readExtensions(FILE* const ifP, 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. + If we hit end of file before an EOD marker, we fail. -----------------------------------------------------------------------------*/ bool imageStart; bool eod; + *errorP = NULL; /* initial value */ + eod = FALSE; imageStart = FALSE; /* Read the image descriptor */ - while (!imageStart && !eod) { + while (!imageStart && !eod && !*errorP) { unsigned char c; + const char * error; - if (! ReadOK(ifP,&c,1)) - pm_error("EOF / read error on image data" ); + readFile(ifP, &c, 1, &error); - 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 ); + if (error) { + asprintfN(errorP, "File read error where start of image " + "descriptor or end of GIF expected. %s", + error); + strfree(error); + } else { + if (c == ';') { /* GIF terminator */ + eod = TRUE; + } else if (c == '!') { /* Extension */ + unsigned char functionCode; + const char * error; + + readFile(ifP, &functionCode, 1, &error); + + if (error) { + asprintfN(errorP, "Failed to read function code " + "of GIF extension (immediately after the '!' " + "extension delimiter) from input. %s", error); + strfree(error); + } else { + doExtension(ifP, functionCode, gif89P); + } + } else if (c == ',') + imageStart = TRUE; + else + pm_message("bogus character 0x%02x, ignoring", (int)c); + } } *eodP = eod; } @@ -1267,9 +1454,9 @@ reportImageInfo(unsigned int const cols, 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 @@ -1284,7 +1471,8 @@ convertImage(FILE * const ifP, FILE * const imageout_file, FILE * const alphafile, struct gifScreen gifScreen, - struct gif89 const gif89) { + struct gif89 const gif89, + bool const tolerateBadInput) { /*---------------------------------------------------------------------------- Read a single GIF image from the current position of file 'ifP'. @@ -1335,7 +1523,8 @@ convertImage(FILE * const ifP, &hasGray, &hasColor); transparencyMessage(gif89.transparent, localColorMap); readImageData(ifP, xels, cols, rows, localColorMap, localColorMapSize, - interlaced, gif89.transparent, alphabits); + interlaced, gif89.transparent, alphabits, + tolerateBadInput); if (!skipIt) { writePnm(imageout_file, xels, cols, rows, hasGray, hasColor); @@ -1344,7 +1533,8 @@ convertImage(FILE * const ifP, transparencyMessage(gif89.transparent, gifScreen.ColorMap); readImageData(ifP, xels, cols, rows, gifScreen.ColorMap, gifScreen.ColorMapSize, - interlaced, gif89.transparent, alphabits); + interlaced, gif89.transparent, alphabits, + tolerateBadInput); if (!skipIt) { writePnm(imageout_file, xels, cols, rows, gifScreen.hasGray, gifScreen.hasColor); @@ -1362,12 +1552,33 @@ convertImage(FILE * const ifP, static void +disposeOfReadExtensionsError(const char * const error, + bool const tolerateBadInput, + unsigned int const imageSeq, + bool * const eodP) { + if (error) { + if (tolerateBadInput) + pm_message("Error accessing Image %u of stream; no further " + "images can be accessed. %s", + imageSeq, error); + else + pm_error("Error accessing Image %u of stream. %s", + imageSeq, error); + strfree(error); + *eodP = TRUE; + } +} + + + +static void convertImages(FILE * const ifP, bool const allImages, int const requestedImageSeq, bool const drainStream, FILE * const imageout_file, - FILE * const alphafile) { + FILE * const alphafile, + bool const tolerateBadInput) { /*---------------------------------------------------------------------------- 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 @@ -1402,20 +1613,25 @@ convertImages(FILE * const ifP, !eod && (imageSeq <= requestedImageSeq || allImages || drainStream); ++imageSeq) { - readExtensions(ifP, &gif89, &eod); + const char * error; + + readExtensions(ifP, &gif89, &eod, &error); + + disposeOfReadExtensionsError(error, tolerateBadInput, imageSeq, &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":"" ); + 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); + imageout_file, alphafile, gifScreen, gif89, + tolerateBadInput); } } } @@ -1448,13 +1664,14 @@ main(int argc, char **argv) { imageout_file = stdout; convertImages(ifP, cmdline.all_images, cmdline.image_no, - !cmdline.quitearly, imageout_file, alpha_file); + !cmdline.quitearly, imageout_file, alpha_file, + cmdline.repair); pm_close(ifP); if (imageout_file != NULL) - pm_close( imageout_file ); + pm_close(imageout_file); if (alpha_file != NULL) - pm_close( alpha_file ); + pm_close(alpha_file); return 0; } diff --git a/converter/other/jpegdatasource.c b/converter/other/jpegdatasource.c index 5c1070e4..1f53c2a4 100644 --- a/converter/other/jpegdatasource.c +++ b/converter/other/jpegdatasource.c @@ -45,6 +45,11 @@ struct sourceManager { */ struct jpeg_source_mgr jpegSourceMgr; FILE * ifP; + bool prematureEof; + /* We have been asked for data and were unable to comply because + the file had no more to give (so we supplied EOI markers + instead). + */ JOCTET * currentBuffer; JOCTET * nextBuffer; unsigned int bytesInNextBuffer; @@ -66,6 +71,10 @@ dsInitSource(j_decompress_ptr const cinfoP) { +static const JOCTET jfifEoiMarker[] = {0xff, JPEG_EOI}; + /* An EOI (end of image) marker */ + + static boolean dsFillInputBuffer(j_decompress_ptr const cinfoP) { /*---------------------------------------------------------------------------- @@ -74,9 +83,19 @@ dsFillInputBuffer(j_decompress_ptr const cinfoP) { -----------------------------------------------------------------------------*/ 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 { + if (srcP->bytesInNextBuffer == 0) { + /* The decompressor expects more bytes, but there aren't any, so + the file is corrupted -- probably truncated. We want the + decompressor to decompress whatever it's read so far, so we + synthesize an EOI marker here, but we also set error state + in the source manager. The decompressor will recognize the + truncation and pad out the image with gray. + */ + srcP->prematureEof = TRUE; + + srcP->jpegSourceMgr.next_input_byte = jfifEoiMarker; + srcP->jpegSourceMgr.bytes_in_buffer = sizeof(jfifEoiMarker); + } else { /* Rotate the buffers */ srcP->jpegSourceMgr.next_input_byte = srcP->nextBuffer; srcP->jpegSourceMgr.bytes_in_buffer = srcP->bytesInNextBuffer; @@ -87,7 +106,7 @@ dsFillInputBuffer(j_decompress_ptr const cinfoP) { srcP->currentBuffer = tmp; } - /* Fill the new 'next' buffer */ + /* Fill the new "next" buffer */ srcP->bytesInNextBuffer = fread(srcP->nextBuffer, 1, BUFFER_SIZE, srcP->ifP); } @@ -139,6 +158,14 @@ dsDataLeft(struct sourceManager * const srcP) { +bool +dsPrematureEof(struct sourceManager * const srcP) { + + return srcP->prematureEof; +} + + + struct sourceManager * dsCreateSource(const char * const fileName) { @@ -156,6 +183,7 @@ dsCreateSource(const char * const fileName) { srcP->jpegSourceMgr.resync_to_restart = jpeg_resync_to_restart; srcP->jpegSourceMgr.term_source = dsTermSource; + srcP->prematureEof = FALSE; srcP->currentBuffer = srcP->buffer1; srcP->nextBuffer = srcP->buffer2; srcP->jpegSourceMgr.bytes_in_buffer = diff --git a/converter/other/jpegdatasource.h b/converter/other/jpegdatasource.h index 07f17389..58648fe4 100644 --- a/converter/other/jpegdatasource.h +++ b/converter/other/jpegdatasource.h @@ -12,6 +12,9 @@ dsDestroySource(struct sourceManager * const srcP); bool dsDataLeft(struct sourceManager * const srcP); +bool +dsPrematureEof(struct sourceManager * const srcP); + struct jpeg_source_mgr * dsJpegSourceMgr(struct sourceManager * const srcP); diff --git a/converter/other/jpegtopnm.c b/converter/other/jpegtopnm.c index 60ae7e42..180ef3d4 100644 --- a/converter/other/jpegtopnm.c +++ b/converter/other/jpegtopnm.c @@ -109,6 +109,7 @@ struct cmdlineInfo { unsigned int comments; unsigned int dumpexif; unsigned int multiple; + unsigned int repair; }; @@ -116,9 +117,9 @@ 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_maxmemory(bool const maxmemorySpec, + const char * const maxmemory, + long int * const max_memory_to_use_p) { /*---------------------------------------------------------------------------- Interpret the "maxmemory" command line option. -----------------------------------------------------------------------------*/ @@ -157,8 +158,9 @@ interpret_adobe(const int adobe, const int notadobe, static void -parse_command_line(const int argc, char ** argv, - struct cmdlineInfo *cmdlineP) { +parseCommandLine(int const argc, + char ** const 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 @@ -173,10 +175,10 @@ parse_command_line(const int argc, char ** argv, */ optStruct3 opt; - int i; /* local loop variable */ + unsigned int i; /* local loop variable */ - char *maxmemory; - char *dctval; + const char * maxmemory; + const char * dctval; unsigned int adobe, notadobe; unsigned int tracelevelSpec, exifSpec, dctvalSpec, maxmemorySpec; @@ -206,6 +208,7 @@ parse_command_line(const int argc, char ** argv, &exifSpec, 0); OPTENT3(0, "dumpexif", OPT_FLAG, NULL, &cmdlineP->dumpexif, 0); OPTENT3(0, "multiple", OPT_FLAG, NULL, &cmdlineP->multiple, 0); + OPTENT3(0, "repair", OPT_FLAG, NULL, &cmdlineP->repair, 0); opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ @@ -213,7 +216,8 @@ parse_command_line(const int argc, char ** argv, /* Make private copy of arguments for optParseOptions to corrupt */ argc_parse = argc; - for (i=0; i < argc; i++) argv_parse[i] = argv[i]; + 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, @@ -458,32 +462,34 @@ read_rgb(JSAMPLE *ptr, const enum colorspace color_space, 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 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); +copyPixelRow(JSAMPROW const jpegbuffer, + unsigned int const width, + unsigned int const samplesPerPixel, + enum colorspace const colorSpace, + FILE * const ofP, + int const format, + xelval const maxval) { + + JSAMPLE * ptr; + unsigned int outputCursor; /* Cursor into output buffer 'pnmbuffer' */ + + ptr = &jpegbuffer[0]; /* Start at beginning of input row */ + + for (outputCursor = 0; outputCursor < width; ++outputCursor) { + xel currentPixel; + if (samplesPerPixel >= 3) { + const rgb_type * const rgb_p = read_rgb(ptr, colorSpace, maxval); + PPM_ASSIGN(currentPixel, rgb_p->r, rgb_p->g, rgb_p->b); + } else { + PNM_ASSIGN1(currentPixel, GETJSAMPLE(*ptr)); + } + ptr += samplesPerPixel; /* move to next pixel of input */ + pnmbuffer[outputCursor] = currentPixel; + } + pnm_writepnmrow(ofP, pnmbuffer, width, maxval, format, FALSE); } @@ -796,17 +802,42 @@ computeColorSpace(struct jpeg_decompress_struct * const cinfoP, static void +convertRaster(struct jpeg_decompress_struct * const cinfoP, + enum colorspace const color_space, + FILE * const ofP, + xelval const format, + unsigned int const maxval) { + + JSAMPROW jpegbuffer; /* Input buffer. Filled by jpeg_scanlines() */ + + jpegbuffer = ((*cinfoP->mem->alloc_sarray) + ((j_common_ptr) cinfoP, JPOOL_IMAGE, + cinfoP->output_width * cinfoP->output_components, + (JDIMENSION) 1) + )[0]; + + while (cinfoP->output_scanline < cinfoP->output_height) { + jpeg_read_scanlines(cinfoP, &jpegbuffer, 1); + if (ofP) + copyPixelRow(jpegbuffer, cinfoP->output_width, + cinfoP->out_color_components, + color_space, ofP, format, maxval); + } +} + + + +static void convertImage(FILE * const ofP, struct cmdlineInfo const cmdline, struct jpeg_decompress_struct * const cinfoP) { - int output_type; + int format; /* 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; + xelval maxval; /* The maximum value of a sample (color component), both in the input and the output. */ @@ -817,43 +848,30 @@ convertImage(FILE * const ofP, cmdline.dct_method, cmdline.max_memory_to_use, cmdline.nosmooth); - set_color_spaces(cinfoP->jpeg_color_space, &output_type, + set_color_spaces(cinfoP->jpeg_color_space, &format, &cinfoP->out_color_space); - maxval = (1 << cinfoP->data_precision) - 1; + maxval = pm_bitstomaxval(cinfoP->data_precision); if (cmdline.verbose) - tellDetails(*cinfoP, maxval, output_type); + tellDetails(*cinfoP, maxval, format); /* 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); + maxval, format, 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); - } + + convertRaster(cinfoP, color_space, ofP, format, maxval); if (cmdline.comments) print_comments(*cinfoP); @@ -905,17 +923,25 @@ convertImages(FILE * const ofP, convertImage(ofP, cmdline, cinfoP); } } else { - if (dsDataLeft(sourceManagerP)) + if (dsDataLeft(sourceManagerP)) { convertImage(ofP, cmdline, cinfoP); - else + } else pm_error("Input stream is empty"); } + if (dsPrematureEof(sourceManagerP)) { + if (cmdline.repair) + pm_message("Premature EOF on input; repaired by padding end " + "of image."); + else + pm_error("Premature EOF on input. Use -repair to salvage."); + } } int main(int argc, char **argv) { + FILE * ofP; struct cmdlineInfo cmdline; struct jpeg_decompress_struct cinfo; @@ -924,7 +950,7 @@ main(int argc, char **argv) { pnm_init(&argc, argv); - parse_command_line(argc, argv, &cmdline); + parseCommandLine(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 */ @@ -967,4 +993,3 @@ main(int argc, char **argv) { exit(jerr.num_warnings > 0 ? EXIT_WARNING : EXIT_SUCCESS); } - diff --git a/converter/other/pamtogif.c b/converter/other/pamtogif.c index 81e8edb4..68a445c6 100644 --- a/converter/other/pamtogif.c +++ b/converter/other/pamtogif.c @@ -21,6 +21,7 @@ static unsigned int const gifMaxval = 255; static bool verbose; + typedef int stringCode; /* A code to be place in the GIF raster. It represents a string of one or more pixels. You interpret this in the context @@ -37,9 +38,6 @@ typedef int stringCode; Ergo, this data structure must be signed and at least BITS bits wide plus sign bit. - - TODO: Some variables that should be of this type are defined as - "long". */ @@ -52,15 +50,6 @@ struct cmap { /* Maps a color index, as is found in the raster part of the GIF, to color. */ - 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[]. - - The sort colormap is the one that goes into the GIF. The - unsorted one is the one that is hashed so we can find colors - in it. - */ unsigned int cmapSize; /* Number of entries in the GIF colormap. I.e. number of colors in the image, plus possibly one fake transparency color. @@ -69,12 +58,12 @@ struct cmap { /* The colormap contains an entry for transparent pixels */ unsigned int transparent; /* color index number in GIF palette of the color that is to - be transparent. This is a post-sort index. + be transparent. Meaningful only if 'haveTransparent' is true. */ tuplehash tuplehash; - /* A hash table to translate color to GIF (presort) colormap index. */ + /* A hash table to translate color to GIF colormap index. */ }; struct cmdlineInfo { @@ -89,22 +78,11 @@ struct cmdlineInfo { const char *transparent; /* -transparent option value. NULL if none. */ const char *comment; /* -comment option value; NULL if none */ unsigned int nolzw; /* -nolzw option */ + float aspect; /* -aspect option value (the ratio). */ unsigned int verbose; }; -static unsigned int -nSignificantBits(unsigned int const arg) { - - unsigned int i; - - i = 0; - - while (arg >> i != 0) - ++i; - - return i; -} @@ -147,6 +125,8 @@ parseCommandLine(int argc, char ** argv, optStruct3 opt; /* set by OPTENT3 */ unsigned int option_def_index; + unsigned int aspectSpec; + MALLOCARRAY_NOFAIL(option_def, 100); option_def_index = 0; /* incremented by OPTENT3 */ @@ -164,8 +144,10 @@ parseCommandLine(int argc, char ** argv, &cmdlineP->comment, NULL, 0); OPTENT3(0, "alphacolor", OPT_STRING, &cmdlineP->alphacolor, NULL, 0); + OPTENT3(0, "aspect", OPT_FLOAT, + &cmdlineP->aspect, &aspectSpec, 0); OPTENT3(0, "verbose", OPT_FLAG, - NULL, &cmdlineP->verbose, 0); + NULL, &cmdlineP->verbose, 0); /* Set the defaults */ cmdlineP->mapfile = NULL; @@ -190,6 +172,19 @@ parseCommandLine(int argc, char ** argv, "specified %d", argc-1); else cmdlineP->input_filespec = argv[1]; + + if (aspectSpec) { + if (cmdlineP->aspect < 0.25 || cmdlineP->aspect > 4.21875) + pm_error("Invalid -aspect value: %f. " + "GIF allows only the range 0.25-4.0 .", + cmdlineP->aspect); + else if (cmdlineP->aspect > 4.0) + pm_message("Warning: " + "You specified an aspect ratio over 4.0: %f. " + "This will result in an invalid GIF.", + cmdlineP->aspect); + } else + cmdlineP->aspect = 1.0; } @@ -211,7 +206,7 @@ closestColor(tuple const color, struct pam * const pamP, struct cmap * const cmapP) { /*---------------------------------------------------------------------------- - Return the pre-sort colormap index of the color in the colormap *cmapP + Return the colormap index of the color in the colormap *cmapP that is closest to the color 'color', whose format is specified by *pamP. @@ -450,24 +445,22 @@ gifPixel(struct pam * const pamP, 'alphaThreshold' is the alpha level below which we consider a pixel transparent for GIF purposes. -----------------------------------------------------------------------------*/ - unsigned int colorIndex; + int colorIndex; if (alphaPlane && tuple[alphaPlane] < alphaThreshold && cmapP->haveTransparent) colorIndex = cmapP->transparent; else { - int presortColorindex; int found; pnm_lookuptuple(pamP, cmapP->tuplehash, tuple, - &found, &presortColorindex); + &found, &colorIndex); if (!found) - presortColorindex = closestColor(tuple, pamP, cmapP); - - colorIndex = cmapP->perm[presortColorindex]; + colorIndex = closestColor(tuple, pamP, cmapP); } - return colorIndex; + assert(colorIndex >= 0); + return (unsigned int) colorIndex; } @@ -536,8 +529,6 @@ writeCommentExtension(FILE * const ofP, #define BITS 12 -#define HSIZE 5003 /* 80% occupancy */ - /* * * GIF Image compression - modified 'compress' @@ -560,7 +551,7 @@ static stringCode const maxCodeLimitLzw = (stringCode)1 << BITS; struct hashTableEntry { - long fcode; /* -1 means unused slot */ + stringCode fcode; /* -1 means unused slot */ unsigned int ent; }; @@ -729,13 +720,6 @@ codeBuffer_increaseCodeSize(codeBuffer * const codeBufferP) { codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1; } - - -static unsigned long const masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, - 0x001F, 0x003F, 0x007F, 0x00FF, - 0x01FF, 0x03FF, 0x07FF, 0x0FFF, - 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; - static void codeBuffer_output(codeBuffer * const codeBufferP, stringCode const code) { @@ -751,10 +735,10 @@ codeBuffer_output(codeBuffer * const codeBufferP, -----------------------------------------------------------------------------*/ assert (code <= codeBufferP->maxCode); - codeBufferP->curAccum &= masks[codeBufferP->curBits]; + codeBufferP->curAccum &= (1 << codeBufferP->curBits) - 1; if (codeBufferP->curBits > 0) - codeBufferP->curAccum |= ((long)code << codeBufferP->curBits); + codeBufferP->curAccum |= ((unsigned long)code << codeBufferP->curBits); else codeBufferP->curAccum = code; @@ -806,6 +790,12 @@ typedef struct { proper data, but always using one code per pixel, and therefore not effecting any compression and not using the LZW patent. */ + unsigned int hsize; + /* The number of slots in the hash table. This variable to + enhance overall performance by reducing memory use when + encoding smaller gifs. + */ + unsigned int hshift; /* This is how many bits we shift left a string code in forming the primary hash of the concatenation of that string with another. @@ -869,28 +859,82 @@ typedef struct { + +static unsigned int +nSignificantBits( unsigned int const arg ){ + +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 304) + + return (arg == 0) ? 0 : 8 * sizeof(unsigned int) - __builtin_clz(arg); + +#else + + unsigned int i = 0; + while (arg >> i != 0) + ++i; + + return i; +#endif +} + + + static lzwCompressor * lzw_create(FILE * const ofP, unsigned int const initBits, - bool const lzw) { + bool const lzw, + unsigned int const pixelCount) { - lzwCompressor * lzwP; + unsigned int const hsizeTable[] = {257, 521, 1031, 2053, 4099, 5003}; + /* If the image has 4096 or fewer pixels we use prime numbers slightly + above powers of two between 8 and 12. In this case the hash table + never fills up; clear code is never emitted. + + Above that we use a table with 4096 slots plus 20% extra. + When this is not enough the clear code is emitted. + Due to the extra 20% the table itself never fills up. + + lzw.hsize and lzw.hshift stay constant through the image. + + Variable hsize is a performance enhancement based on the fact that + the encoder never needs more codes than the number of pixels in + the image. Typically, the ratio of pixels to codes is around + 10:1 to 20:1. + + Logic works with fixed values lzw.hsize=5003 and t=13. + */ + lzwCompressor * lzwP; + MALLOCVAR_NOFAIL(lzwP); - MALLOCARRAY(lzwP->hashTable, HSIZE); - - if (lzwP->hashTable == NULL) - pm_error("Couldn't get memory for %u-entry hash table.", HSIZE); - /* Constants */ lzwP->lzw = lzw; - lzwP->hshift = - nSignificantBits(HSIZE-1) - 1 - nSignificantBits(MAXCMAPSIZE-1); - lzwP->clearCode = 1 << (initBits - 1); - lzwP->eofCode = lzwP->clearCode + 1; + + lzwP->clearCode = 1 << (initBits - 1); + lzwP->eofCode = lzwP->clearCode + 1; lzwP->initCodeLimit = 1 << initBits; + if (lzw) { + unsigned int const t = + MIN(13, MAX(8, nSignificantBits(pixelCount +lzwP->eofCode - 2))); + /* Index into hsizeTable */ + + lzwP->hsize = hsizeTable[t-8]; + + lzwP->hshift = (t == 13 ? 12 : t) - nSignificantBits(MAXCMAPSIZE-1); + + MALLOCARRAY(lzwP->hashTable, lzwP->hsize); + + if (lzwP->hashTable == NULL) + pm_error("Couldn't get memory for %u-entry hash table.", + lzwP->hsize); + } else { + /* No LZW compression. We don't need a stringcode hash table */ + lzwP->hashTable = NULL; + lzwP->hsize = 0; + } + lzwP->buildingString = FALSE; lzwP->codeBufferP = codeBuffer_create(ofP, initBits, lzw); @@ -919,12 +963,10 @@ lzwHashClear(lzwCompressor * const lzwP) { unsigned int i; - for (i = 0; i < HSIZE; ++i) + for (i = 0; i < lzwP->hsize; ++i) lzwP->hashTable[i].fcode = -1; lzwP->nextUnusedCode = lzwP->clearCode + 2; - - } @@ -1049,9 +1091,7 @@ primaryHash(stringCode const baseString, assert(additionalPixel < MAXCMAPSIZE); hash = (additionalPixel << hshift) ^ baseString; - - assert(hash < HSIZE); - + return hash; } @@ -1060,7 +1100,7 @@ primaryHash(stringCode const baseString, static void lookupInHash(lzwCompressor * const lzwP, unsigned int const gifPixel, - long const fcode, + stringCode const fcode, bool * const foundP, unsigned short * const codeP, unsigned int * const hashP) { @@ -1071,13 +1111,13 @@ lookupInHash(lzwCompressor * const lzwP, /* Index into hash table */ i = primaryHash(lzwP->stringSoFar, gifPixel, lzwP->hshift); - disp = (i == 0) ? 1 : HSIZE - i; + disp = (i == 0) ? 1 : lzwP->hsize - i; while (lzwP->hashTable[i].fcode != fcode && lzwP->hashTable[i].fcode >= 0) { i -= disp; if (i < 0) - i += HSIZE; + i += lzwP->hsize; } if (lzwP->hashTable[i].fcode == fcode) { @@ -1109,9 +1149,10 @@ lzw_encodePixel(lzwCompressor * const lzwP, lzwP->stringSoFar = gifPixel; lzwP->buildingString = TRUE; } else { - long const fcode = ((long) gifPixel << BITS) + lzwP->stringSoFar; + stringCode const fcode = + ((stringCode) gifPixel << BITS) + lzwP->stringSoFar; /* The encoding of the string we've already recognized plus the - instant pixel, to be looked up in the hash of knows strings. + instant pixel, to be looked up in the hash of known strings. */ lookupInHash(lzwP, gifPixel, fcode, &found, &code, &hash); @@ -1196,7 +1237,7 @@ writeRaster(struct pam * const pamP, number of the current row. */ - lzwP = lzw_create(ofP, initBits, lzw); + lzwP = lzw_create(ofP, initBits, lzw, pamP->height * pamP->width); tuplerow = pnm_allocpamrow(pamP); @@ -1236,12 +1277,6 @@ writeRaster(struct pam * const pamP, - - - - - - static void writeGlobalColorMap(FILE * const ofP, const struct cmap * const cmapP, @@ -1272,9 +1307,9 @@ writeGlobalColorMap(FILE * const ofP, for (i = 0; i < colorMapSize; ++i) { if (i < cmapP->cmapSize) { - tuple const color = cmapP->color[cmapP->permi[i]]; + tuple const color = cmapP->color[i]; - assert(cmapP->permi[i] < cmapP->cmapSize); + assert(i < cmapP->cmapSize); pnm_scaletuple(&pam, tupleRgb255, color, 255); pnm_maketuplergb(&pam, tupleRgb255); @@ -1300,14 +1335,15 @@ writeGifHeader(FILE * const ofP, unsigned int const background, unsigned int const bitsPerPixel, const struct cmap * const cmapP, - char const comment[]) { + char const comment[], + float const aspect) { unsigned int const resolution = bitsPerPixel; unsigned char b; /* Write the Magic header */ - if (cmapP->haveTransparent || comment) + if (cmapP->haveTransparent || comment || aspect != 1.0 ) fwrite("GIF89a", 1, 6, ofP); else fwrite("GIF87a", 1, 6, ofP); @@ -1332,9 +1368,11 @@ writeGifHeader(FILE * const ofP, assert((unsigned char)background == background); fputc(background, ofP); - /* Byte of 0's (future expansion) */ - fputc(0x00, ofP); - + { + int const aspectValue = aspect == 1.0 ? 0 : ROUND(aspect * 64) - 15; + assert(0 <= aspectValue && aspectValue <= 255); + fputc(aspectValue, ofP); + } writeGlobalColorMap(ofP, cmapP, bitsPerPixel); if (cmapP->haveTransparent) @@ -1398,6 +1436,7 @@ gifEncode(struct pam * const pamP, unsigned int const bitsPerPixel, struct cmap * const cmapP, char const comment[], + float const aspect, bool const lzw) { unsigned int const leftOffset = 0; @@ -1420,13 +1459,13 @@ gifEncode(struct pam * const pamP, if (pamP->width > 65535) pm_error("Image width %u too large for GIF format. (Max 65535)", pamP->width); - + if (pamP->height > 65535) pm_error("Image height %u too large for GIF format. (Max 65535)", pamP->height); writeGifHeader(ofP, pamP->width, pamP->height, background, - bitsPerPixel, cmapP, comment); + bitsPerPixel, cmapP, comment, aspect); /* Write an Image separator */ fputc(',', ofP); @@ -1457,7 +1496,7 @@ reportTransparent(struct cmap * const cmapP) { if (verbose) { if (cmapP->haveTransparent) { - tuple const color = cmapP->color[cmapP->permi[cmapP->transparent]]; + tuple const color = cmapP->color[cmapP->transparent]; pm_message("Color %u (%lu, %lu, %lu) is transparent", cmapP->transparent, color[PAM_RED_PLANE], @@ -1505,7 +1544,7 @@ computeTransparent(char const colorarg[], bool exact; tuple transcolor; bool found; - int presortColorindex; + int colorindex; if (colorarg[0] == '=') { colorspec = &colorarg[1]; @@ -1517,15 +1556,14 @@ computeTransparent(char const colorarg[], transcolor = pnm_parsecolor(colorspec, cmapP->pam.maxval); pnm_lookuptuple(&cmapP->pam, cmapP->tuplehash, transcolor, &found, - &presortColorindex); + &colorindex); if (found) { cmapP->haveTransparent = TRUE; - cmapP->transparent = cmapP->perm[presortColorindex]; + cmapP->transparent = colorindex; } else if (!exact) { cmapP->haveTransparent = TRUE; - cmapP->transparent = - cmapP->perm[closestColor(transcolor, &cmapP->pam, cmapP)]; + cmapP->transparent = closestColor(transcolor, &cmapP->pam, cmapP); } else { cmapP->haveTransparent = FALSE; pm_message("Warning: specified transparent color " @@ -1543,61 +1581,66 @@ computeTransparent(char const colorarg[], static unsigned int -sortOrder(tuple const tuple, - struct pam * const pamP) { - - if (pamP->depth < 3) - return tuple[0]; - else - return ((tuple[0] * MAXCMAPSIZE) + tuple[1]) * MAXCMAPSIZE + tuple[2]; +sortOrderColor(tuple const tuple) { + + return ((tuple[PAM_RED_PLANE] * MAXCMAPSIZE) + + tuple[PAM_GRN_PLANE]) * MAXCMAPSIZE + + tuple[PAM_BLU_PLANE]; +} + + + +#ifndef LITERAL_FN_DEF_MATCH +static qsort_comparison_fn sortCompareColor; +#endif + +static int +sortCompareColor(const void * const entry1P, + const void * const entry2P) { + + struct tupleint * const * const tupleint1PP = entry1P; + struct tupleint * const * const tupleint2PP = entry2P; + + return (sortOrderColor((*tupleint1PP)->tuple) + - sortOrderColor((*tupleint2PP)->tuple)); } +#ifndef LITERAL_FN_DEF_MATCH +static qsort_comparison_fn sortCompareGray; +#endif + +static int +sortCompareGray(const void * const entry1P, + const void * const entry2P){ + + struct tupleint * const * const tupleint1PP = entry1P; + struct tupleint * const * const tupleint2PP = entry2P; + + return ((*tupleint1PP)->tuple[0] - (*tupleint2PP)->tuple[0]); +} + + static void -sortColormap(bool const sort, - struct cmap * const cmapP) { +sortTupletable(struct pam * const mapPamP, + unsigned int const colors, + tupletable const tuplefreq) { /*---------------------------------------------------------------------------- - Sort (in place) the colormap *cmapP. + Sort 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. + Sort the colormap by red intensity, then by green intensity, + then by blue intensity. -----------------------------------------------------------------------------*/ - tuple * const color = cmapP->color; - int * const perm = cmapP->perm; - int * const permi = cmapP->permi; - struct pam * const pamP = &cmapP->pam; - unsigned int const cmapSize = cmapP->cmapSize; - - unsigned int i; - /* We will sort permi[]. So we first set up permi[] to reflect the - original, unsorted order. - */ - for (i=0; i < cmapSize; i++) - permi[i] = i; - - if (sort) { - pm_message("sorting colormap"); - for (i = 0; i < cmapSize; ++i) { - unsigned int j; - for (j = i+1; j < cmapSize; ++j) { - if (sortOrder(color[permi[i]], pamP) > - sortOrder(color[permi[j]], pamP)) { - - sample tmp; - - tmp = permi[i]; permi[i] = permi[j]; permi[j] = tmp; - } - } - } - } - for (i = 0; i < cmapSize; ++i) - perm[permi[i]] = i; + pm_message("sorting colormap"); + + if (mapPamP->depth < 3) + qsort(tuplefreq, colors, sizeof(tuplefreq[0]), sortCompareGray); + else + qsort(tuplefreq, colors, sizeof(tuplefreq[0]), sortCompareColor); + } @@ -1660,7 +1703,7 @@ colormapFromFile(char const filespec[], pm_message("computing other colormap ..."); - *tupletableP = + *tupletableP = pnm_computetuplefreqtable(mapPamP, colors, maxcolors, &colorCount); *colorCountP = colorCount; @@ -1671,6 +1714,60 @@ colormapFromFile(char const filespec[], static void +readAndValidateColormapFromFile(char const filename[], + unsigned int const maxcolors, + tupletable * const tuplefreqP, + struct pam * const mapPamP, + unsigned int * const colorCountP, + unsigned int const nInputComp, + sample const inputMaxval) { +/*---------------------------------------------------------------------------- + Read the colormap from a separate colormap file named filename[], + and make sure it's consistent with an image with 'nInputComp' + color components (e.g. 3 for RGB) and a maxval of 'inputMaxval'. +-----------------------------------------------------------------------------*/ + colormapFromFile(filename, maxcolors, tuplefreqP, mapPamP, colorCountP); + + if (mapPamP->depth != nInputComp) + pm_error("Depth of map file (%u) does not match number of " + "color components in input file (%u)", + mapPamP->depth, nInputComp); + if (mapPamP->maxval != inputMaxval) + pm_error("Maxval of map file (%lu) does not match maxval of " + "input file (%lu)", mapPamP->maxval, inputMaxval); +} + + + +static void +computeColormapBw(struct pam * const pamP, + struct pam * const mapPamP, + unsigned int * const colorCountP, + tupletable * const tuplefreqP) { +/*---------------------------------------------------------------------------- + Shortcut for black and white (e.g. PBM). We know that there are + only two colors. Users who know that only one color is present in + the image should specify -sort at the command line. Example: + + $ pbmmake -w 600 400 | pamtogif -sort > canvas.gif +-----------------------------------------------------------------------------*/ + tupletable const colormap = pnm_alloctupletable(pamP, 2); + + *mapPamP = *pamP; + mapPamP->depth = 1; + + colormap[0]->value = 1; + colormap[0]->tuple[0] = PAM_BLACK; + colormap[1]->value = 1; + colormap[1]->tuple[0] = PAM_BW_WHITE; + + *tuplefreqP = colormap; + *colorCountP = 2; +} + + + +static void computeColormapFromInput(struct pam * const pamP, unsigned int const maxcolors, unsigned int const nInputComp, @@ -1700,7 +1797,8 @@ computeLibnetpbmColormap(struct pam * const pamP, tuple * const color, tuplehash * const tuplehashP, struct pam * const mapPamP, - unsigned int * const colorCountP) { + unsigned int * const colorCountP, + bool const sort) { /*---------------------------------------------------------------------------- Compute a colormap, libnetpbm style, for the image described by 'pamP', which is positioned to the raster. @@ -1719,6 +1817,9 @@ computeLibnetpbmColormap(struct pam * const pamP, too many, allow one slot for a fake transparency color if 'haveAlpha' is true. If there are too many, issue an error message and abort the program. + + 'sort' means to sort the colormap by red intensity, then by green + intensity, then by blue intensity, as opposed to arbitrary order. -----------------------------------------------------------------------------*/ unsigned int const maxcolors = haveAlpha ? MAXCMAPSIZE - 1 : MAXCMAPSIZE; /* The most colors we can tolerate in the image. If we have @@ -1732,19 +1833,14 @@ computeLibnetpbmColormap(struct pam * const pamP, tupletable tuplefreq; unsigned int colorCount; - if (mapfile) { - /* Read the colormap from a separate colormap file. */ - colormapFromFile(mapfile, maxcolors, &tuplefreq, mapPamP, - &colorCount); - - if (mapPamP->depth != nInputComp) - pm_error("Depth of map file (%u) does not match number of " - "color components in input file (%u)", - mapPamP->depth, pamP->depth); - if (mapPamP->maxval != pamP->maxval) - pm_error("Maxval of map file (%lu) does not match maxval of " - "input file (%lu)", mapPamP->maxval, pamP->maxval); - } else + if (mapfile) + readAndValidateColormapFromFile(mapfile, maxcolors, &tuplefreq, + mapPamP, &colorCount, + nInputComp, pamP->maxval); + else if (nInputComp == 1 && pamP->maxval == 1 && !sort && + pamP->height * pamP->width > 1) + computeColormapBw(pamP, mapPamP, &colorCount, &tuplefreq); + else computeColormapFromInput(pamP, maxcolors, nInputComp, mapPamP, &colorCount, &tuplefreq); @@ -1753,6 +1849,9 @@ computeLibnetpbmColormap(struct pam * const pamP, pm_message("%u colors found", colorCount); + if (sort) + sortTupletable(mapPamP, colorCount, tuplefreq); + for (i = 0; i < colorCount; ++i) { color[i] = pnm_allocpamtuple(mapPamP); pnm_assigntuple(mapPamP, color[i], tuplefreq[i]->tuple); @@ -1812,7 +1911,7 @@ main(int argc, char *argv[]) { computeLibnetpbmColormap(&pam, !!pamAlphaPlane(&pam), cmdline.mapfile, cmap.color, &cmap.tuplehash, - &cmap.pam, &cmap.cmapSize); + &cmap.pam, &cmap.cmapSize, cmdline.sort); assert(cmap.pam.maxval == pam.maxval); @@ -1822,9 +1921,8 @@ main(int argc, char *argv[]) { */ addToColormap(&cmap, cmdline.alphacolor, &fakeTransparent); } - sortColormap(cmdline.sort, &cmap); - bitsPerPixel = pm_maxvaltobits(cmap.cmapSize-1); + bitsPerPixel = cmap.cmapSize == 1 ? 1 : nSignificantBits(cmap.cmapSize-1); computeTransparent(cmdline.transparent, !!pamAlphaPlane(&pam), fakeTransparent, &cmap); @@ -1832,7 +1930,7 @@ main(int argc, char *argv[]) { /* All set, let's do it. */ gifEncode(&pam, stdout, rasterPos, cmdline.interlace, 0, bitsPerPixel, &cmap, cmdline.comment, - !cmdline.nolzw); + cmdline.aspect, !cmdline.nolzw); destroyCmap(&cmap); @@ -1856,6 +1954,10 @@ main(int argc, char *argv[]) { JPEG Group's djpeg on 2001.09.29. In 2006.12 the output subroutines were rewritten; now no uncompressed output subroutines are derived from the Independent JPEG Group's source code. + + 2007.01 Changed sort routine to qsort. (afu) + 2007.03 Implemented variable hash table size, PBM color table + shortcut and "-aspect" command line option. (afu) Copyright (C) 1989 by Jef Poskanzer. @@ -1871,3 +1973,4 @@ main(int argc, char *argv[]) { CompuServe Incorporated. GIF(sm) is a Service Mark property of CompuServe Incorporated. ============================================================================*/ + diff --git a/converter/other/pamtoxvmini.c b/converter/other/pamtoxvmini.c index 3207b0d5..e1aa9b52 100644 --- a/converter/other/pamtoxvmini.c +++ b/converter/other/pamtoxvmini.c @@ -67,7 +67,6 @@ makeXvPalette(xvPalette * const xvPaletteP) { } } } - } @@ -237,7 +236,7 @@ main(int argc, struct pam pam; xvPalette xvPalette; - ppm_init(&argc, argv); + pnm_init(&argc, argv); parseCommandLine(argc, argv, &cmdline); diff --git a/converter/other/pnmtojpeg.c b/converter/other/pnmtojpeg.c index 5a4c5511..eb57bcc5 100644 --- a/converter/other/pnmtojpeg.c +++ b/converter/other/pnmtojpeg.c @@ -1012,10 +1012,9 @@ convert_scanlines(struct jpeg_compress_struct * const cinfo_p, } pnm_freerow(pnm_buffer); - /* Dont' worry about the compressor input buffer; it gets freed + /* Don't worry about the compressor input buffer; it gets freed automatically */ - } diff --git a/converter/other/pnmtops.c b/converter/other/pnmtops.c index 9f6de526..85d5d36d 100644 --- a/converter/other/pnmtops.c +++ b/converter/other/pnmtops.c @@ -496,7 +496,7 @@ destroyBmepsOutputEncoder(struct bmepsoe * const bmepsoeP) { static void outputBmepsSample(struct bmepsoe * const bmepsoeP, unsigned int const sampleValue, - unsigned int const bitsPerSample) { + unsigned int const bitsPerSample) { if (bitsPerSample == 8) oe_byte_add(bmepsoeP->oeP, sampleValue); @@ -1300,7 +1300,7 @@ main(int argc, char * argv[]) { strfree(name); pm_close(ifp); - + return 0; } diff --git a/converter/other/xwdtopnm.c b/converter/other/xwdtopnm.c index 28c38cfc..1d8e1b64 100644 --- a/converter/other/xwdtopnm.c +++ b/converter/other/xwdtopnm.c @@ -14,6 +14,12 @@ 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. + + It has been a real challenge trying to reverse engineer the XWD + format. This program is almost always broken as people find XWD images + with which it does not work and we update the program in response. + + We consider an XWD file correct if Xwud displays it properly. */ @@ -32,11 +38,18 @@ #include "x10wd.h" #include "x11wd.h" +struct compMask { + unsigned long red; + unsigned long grn; + unsigned long blu; +}; + + struct cmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ - char *input_filename; + const char * inputFilename; unsigned int verbose; unsigned int debug; unsigned int headerdump; @@ -119,12 +132,12 @@ parseCommandLine(int argc, char ** argv, /* Uses and sets argc, argv, and some of *cmdlineP and others. */ if (argc - 1 == 0) - cmdlineP->input_filename = NULL; /* he wants stdin */ + cmdlineP->inputFilename = NULL; /* he wants stdin */ else if (argc - 1 == 1) { if (STREQ(argv[1], "-")) - cmdlineP->input_filename = NULL; /* he wants stdin */ + cmdlineP->inputFilename = NULL; /* he wants stdin */ else - cmdlineP->input_filename = strdup(argv[1]); + cmdlineP->inputFilename = strdup(argv[1]); } else pm_error("Too many arguments. The only argument accepted\n" "is the input file specification"); @@ -137,16 +150,14 @@ processX10Header(X10WDFileHeader * const h10P, FILE * const file, int * const colsP, int * const rowsP, - int * const padrightP, + unsigned 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, + struct compMask * const compMaskP, enum byteorder * const byte_orderP, enum byteorder * const bit_orderP) { @@ -215,7 +226,7 @@ processX10Header(X10WDFileHeader * const h10P, PNM_ASSIGN1( (*colorsP)[0], 0 ); PNM_ASSIGN1( (*colorsP)[1], *maxvalP ); *padrightP = - ( ( h10P->pixmap_width + 15 ) / 16 ) * 16 - h10P->pixmap_width; + (((h10P->pixmap_width + 15) / 16) * 16 - h10P->pixmap_width) * 8; *bits_per_itemP = 16; *bits_per_pixelP = 1; } else if ( h10P->window_ncolors == 0 ) { @@ -227,7 +238,7 @@ processX10Header(X10WDFileHeader * const h10P, for ( i = 0; i <= *maxvalP; ++i ) PNM_ASSIGN1( (*colorsP)[i], i ); *padrightP = - ( ( h10P->pixmap_width + 15 ) / 16 ) * 16 - h10P->pixmap_width; + (((h10P->pixmap_width + 15) / 16) * 16 - h10P->pixmap_width) * 8; *bits_per_itemP = 16; *bits_per_pixelP = 1; } else { @@ -245,7 +256,7 @@ processX10Header(X10WDFileHeader * const h10P, x10colors[i].blue); } - *padrightP = h10P->pixmap_width & 1; + *padrightP = (h10P->pixmap_width & 1) * 8; *bits_per_itemP = 8; *bits_per_pixelP = 8; } @@ -426,21 +437,65 @@ dumpX11Header(X11WDFileHeader * const h11P) { +static unsigned long +reverseBits(unsigned long arg, + unsigned int nSigBits) { + + unsigned long input; + unsigned long output; + unsigned int i; + + for (i = 0, input = arg, output = 0; i < nSigBits; ++i) { + output <<= 1; + + output |= (input & 0x1); + + input >>= 1; + } + return output; +} + + + +static void +computeComponentMasks(X11WDFileHeader * const h11P, + struct compMask * const compMaskP) { +/*---------------------------------------------------------------------------- + You'd think the component (red, green, blue) masks in the header + would just be right. But we've seen a direct color image which has + BGR layout even though the masks say RGB. It also says bit order + is LSB first, even though the pixels within the items are arranged + MSB first. So we're guessing that LSB first bit order in that + particular case means the bits within each the pixel are backwards. + So we reverse the masks to compensate. +-----------------------------------------------------------------------------*/ + if (h11P->visual_class == DirectColor && + h11P->bits_per_pixel == 24 && h11P->bitmap_bit_order == LSBFirst) { + compMaskP->red = reverseBits(h11P->red_mask, 24); + compMaskP->grn = reverseBits(h11P->green_mask, 24); + compMaskP->blu = reverseBits(h11P->blue_mask, 24); + } else { + compMaskP->red = h11P->red_mask; + compMaskP->grn = h11P->green_mask; + compMaskP->blu = h11P->blue_mask; + } +} + + + static void processX11Header(X11WDFileHeader * const h11P, FILE * const file, int * const colsP, int * const rowsP, - int * const padrightP, + unsigned 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, + struct compMask * const compMaskP, enum byteorder * const byte_orderP, enum byteorder * const bit_orderP) { @@ -460,34 +515,31 @@ processX11Header(X11WDFileHeader * const h11P, 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 ); + 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 ); + 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 ) { + if (*visualclassP == DirectColor) { unsigned int i; *formatP = PPM_TYPE; *maxvalP = 65535; @@ -497,12 +549,12 @@ processX11Header(X11WDFileHeader * const h11P, is composed of 3 separate indices. */ - *colorsP = pnm_allocrow( h11FixedP->ncolors ); - for ( i = 0; i < h11FixedP->ncolors; ++i ) + *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 ) { + } else if (*visualclassP == TrueColor) { *formatP = PPM_TYPE; *maxvalP = pm_lcm(pm_bitstomaxval(one_bits(h11FixedP->red_mask)), @@ -510,31 +562,30 @@ processX11Header(X11WDFileHeader * const h11P, pm_bitstomaxval(one_bits(h11FixedP->blue_mask)), PPM_OVERALLMAXVAL ); - } - else if ( *visualclassP == StaticGray && h11FixedP->bits_per_pixel == 1 ) { + } 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 ) { + 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 ); + *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 ) { + *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 ); + 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 ) + for (i = 0; i < h11FixedP->ncolors; ++i) PPM_ASSIGN( (*colorsP)[i], x11colors[i].red, x11colors[i].green, x11colors[i].blue); @@ -545,13 +596,14 @@ processX11Header(X11WDFileHeader * const h11P, *colsP = h11FixedP->pixmap_width; *rowsP = h11FixedP->pixmap_height; *padrightP = - h11FixedP->bytes_per_line * 8 / h11FixedP->bits_per_pixel - - h11FixedP->pixmap_width; + h11FixedP->bytes_per_line * 8 - + h11FixedP->pixmap_width * h11FixedP->bits_per_pixel; + /* 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. + See comments in pixelReader_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 @@ -561,22 +613,38 @@ processX11Header(X11WDFileHeader * const h11P, 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. + In March 2007, Darren Frith presented an xwd file like this: + Header says direct color, bits_per_pixel = 24, bitmap_unit = + 32, bitmap_pad = 8, byte order and bit order LSB first. The + bytes in each item are in fact MSB first and the pixels spread + across the items MSB first. The raster is consecutive 24 bit + pixel units, but each row is padded on the right with enough + bits to make the total line size 32 x width. Really strange. + The header says the bits within each pixel are one byte red, + one byte green, one byte blue. But they are actually blue, + green, red. Xwud, ImageMagick, and Gimp render this image + correctly, so it's not broken. Created by Xwd of X.org 7.1.1. + 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); - + 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; + if (*visualclassP == DirectColor) { + /* Strange, but we've seen a Direct Color 24/32 image that + says LSBFirst and it's a lie. And Xwud renders it correctly. + */ + *byte_orderP = MSBFirst; + *bit_orderP = MSBFirst; + } else { + *byte_orderP = (enum byteorder) h11FixedP->byte_order; + *bit_orderP = (enum byteorder) h11FixedP->bitmap_bit_order; + } + computeComponentMasks(h11FixedP, compMaskP); free(h11FixedP); } @@ -587,16 +655,14 @@ static void getinit(FILE * const ifP, int * const colsP, int * const rowsP, - int * const padrightP, + unsigned 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, + struct compMask * const compMaskP, enum byteorder * const byte_orderP, enum byteorder * const bit_orderP, bool const headerDump) { @@ -606,10 +672,10 @@ getinit(FILE * const ifP, Return various fields from the header. - Return as *padrightP the number of additional pixels of padding are + Return as *padrightP the number of additional bits 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. + contains *colsP pixels of image data (at *bits_per_pixelP bits each) + plus *padrightP bits of padding. -----------------------------------------------------------------------------*/ /* Assume X11 headers are larger than X10 ones. */ unsigned char header[sizeof(X11WDFileHeader)]; @@ -643,8 +709,7 @@ getinit(FILE * const ifP, 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); + compMaskP, byte_orderP, bit_orderP); } else if (h11P->file_version == X11WD_FILE_VERSION || pm_bs_long(h11P->file_version) == X11WD_FILE_VERSION) { @@ -665,8 +730,7 @@ getinit(FILE * const ifP, 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); + compMaskP, byte_orderP, bit_orderP); } else pm_error("unknown XWD file version: %u", h11P->file_version); } @@ -719,13 +783,10 @@ getinit(FILE * const ifP, one pixel at a time from it. It consists of a structure of type 'pixelReader' and the - getpix() and pixelReaderInit() subroutines. + pixelReader_*() 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 @@ -770,12 +831,12 @@ typedef struct { static void -pixelReaderInit(pixelReader * const pixelReaderP, - FILE * const fileP, - int const bitsPerPixel, - int const bitsPerItem, - enum byteorder const byteOrder, - enum byteorder const bitOrder) { +pixelReader_init(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; @@ -789,6 +850,33 @@ pixelReaderInit(pixelReader * const pixelReaderP, static void +pixelReader_term(pixelReader * const pixelReaderP) { + + uint remainingByteCount; + + if (pixelReaderP->nBitsLeft > 0) + pm_message("Warning: %u unused bits left in the pixel reader " + "buffer after full image converted. XWD file may be " + "corrupted or Xwdtopnm may have misinterpreted it", + pixelReaderP->nBitsLeft); + + + pm_drain(pixelReaderP->fileP, 4096, &remainingByteCount); + + if (remainingByteCount >= 4096) + pm_message("Warning: at least 4K additional bytes in XWD input stream " + "after full image converted. XWD file may be corrupted " + "or Xwdtopnm may have misinterpreted it."); + else if (remainingByteCount > 0) + pm_message("Warning: %u additional bytes in XWD input stream " + "after full image converted. XWD file may be corrupted " + "or Xwdtopnm may have misinterpreted it.", + remainingByteCount); +} + + + +static void readItem(pixelReader * const rdrP) { /*---------------------------------------------------------------------------- Read one item from the XWD raster associated with pixel reader *rdrP. @@ -861,55 +949,16 @@ static unsigned long const lsbmask[] = { static unsigned long -getpix(pixelReader * const rdrP) { +pixelReader_getbits(pixelReader * const rdrP, + unsigned int const nBits) { /*---------------------------------------------------------------------------- - 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. - + Get the next 'nBits' bits from the stream, and return the last 32 + of them. -----------------------------------------------------------------------------*/ 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 . + accumulator is rdrP->bitsPerPixel - nBitsStillNeeded . */ unsigned int nBitsStillNeeded; @@ -919,10 +968,8 @@ getpix(pixelReader * const rdrP) { it -- additional bits will shift in from the right. */ - assert(rdrP->bitsPerPixel <= 32); - pixel = 0; - nBitsStillNeeded = rdrP->bitsPerPixel; + nBitsStillNeeded = nBits; while (nBitsStillNeeded > 0) { if (rdrP->nBitsLeft == 0) @@ -964,18 +1011,65 @@ getpix(pixelReader * const rdrP) { +static unsigned long +pixelReader_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. We have seen a Direct Color 24 bpp/32 bpi + image which said 'byte_order' == LSBFirst, but the byte order is + nonetheless MSB first. + + 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. We have also seen images in which the pixels are arranged + from left to right within the items, but the RGB components within + each pixel are right to left and 'bit_order' is LSBFirst. + + But it's not that simple. Sometimes dummy bits are added to the + right edge of the image in order to make an integral number of + items in each row of the raster. And we've even seen images where + there are a ridiculous number of padding bits on the right so as to + make the number of items per line equal the number of pixels per + line, even though items are 32 bits and pixels are 24 bits! 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. + + We maintain a 32 bit buffer to decouple reading of whole items from + the file and reading of an arbitrary number of bits from the + pixelReader. +-----------------------------------------------------------------------------*/ + assert(rdrP->bitsPerPixel <= 32); + + return pixelReader_getbits(rdrP, rdrP->bitsPerPixel); +} + + + static void reportInfo(int const cols, int const rows, - int const padright, + unsigned 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, + struct compMask const compMask, enum byteorder const byte_order, enum byteorder const bit_order) { @@ -1003,15 +1097,15 @@ reportInfo(int const cols, } pm_message("%d rows of %d columns with maxval %d", rows, cols, maxval); - pm_message("padright=%d. visualclass = %s. format=%d (%c%c)", + pm_message("padright=%u bits. 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); + pm_message("component mask: red=0x%.8lx; grn=0x%.8lx; blu=0x%.8lx", + compMask.red, compMask.grn, compMask.blu); } @@ -1024,36 +1118,34 @@ convertRowSimpleIndex(pixelReader * const pixelReaderP, unsigned int col; for (col = 0; col < cols; ++col) - xelrow[col] = colors[getpix(pixelReaderP)]; + xelrow[col] = colors[pixelReader_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) { +convertRowDirect(pixelReader * const pixelReaderP, + int const cols, + const xel * const colors, + struct compMask const compMask, + 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. + into this bit string according to compMask */ unsigned int red_index, grn_index, blu_index; /* These are indices into the color map, unpacked from 'pixel'. */ - pixel = getpix(pixelReaderP); + pixel = pixelReader_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); + red_index = (pixel & compMask.red) >> zero_bits(compMask.red); + grn_index = (pixel & compMask.grn) >> zero_bits(compMask.grn); + blu_index = (pixel & compMask.blu) >> zero_bits(compMask.blu); PPM_ASSIGN(xelrow[col], PPM_GETR(colors[red_index]), @@ -1066,39 +1158,37 @@ convertRowDirect(pixelReader * const pixelReaderP, 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) { +convertRowTrueColor(pixelReader * const pixelReaderP, + int const cols, + pixval const maxval, + const xel * const colors, + struct compMask const compMask, + 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_shift = zero_bits(compMask.red); + grn_shift = zero_bits(compMask.grn); + blu_shift = zero_bits(compMask.blu); - red_maxval = red_mask >> red_shift; - grn_maxval = grn_mask >> grn_shift; - blu_maxval = blu_mask >> blu_shift; + red_maxval = compMask.red >> red_shift; + grn_maxval = compMask.grn >> grn_shift; + blu_maxval = compMask.blu >> blu_shift; for (col = 0; col < cols; ++col) { unsigned long pixel; - pixel = getpix(pixelReaderP); + pixel = pixelReader_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 + ((pixel & compMask.red) >> red_shift) * maxval / red_maxval, + ((pixel & compMask.grn) >> grn_shift) * maxval / grn_maxval, + ((pixel & compMask.blu) >> blu_shift) * maxval / blu_maxval ); } @@ -1109,13 +1199,11 @@ convertRowTrueColor(pixelReader * const pixelReaderP, static void convertRow(pixelReader * const pixelReaderP, FILE * const ofP, - int const padright, + unsigned 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, + struct compMask const compMask, const xel* const colors, enum visualclass const visualclass) { /*---------------------------------------------------------------------------- @@ -1125,7 +1213,7 @@ convertRow(pixelReader * const pixelReaderP, 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 + 'padright' bits from the input stream, so as to read the entire input line. -----------------------------------------------------------------------------*/ xel* xelrow; @@ -1139,25 +1227,19 @@ convertRow(pixelReader * const pixelReaderP, convertRowSimpleIndex(pixelReaderP, cols, colors, xelrow); break; case DirectColor: - convertRowDirect(pixelReaderP, cols, colors, - red_mask, green_mask, blue_mask, - xelrow); + convertRowDirect(pixelReaderP, cols, colors, compMask, xelrow); break; case TrueColor: convertRowTrueColor(pixelReaderP, cols, maxval, colors, - red_mask, green_mask, blue_mask, - xelrow); + compMask, xelrow); break; default: pm_error("unknown visual class"); } - { - unsigned int col; - for (col = 0; col < padright; ++col) - getpix(pixelReaderP); - } + pixelReader_getbits(pixelReaderP, padright); + pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0); pnm_freerow(xelrow); } @@ -1192,15 +1274,17 @@ main(int argc, char *argv[]) { struct cmdlineInfo cmdline; FILE * ifP; - int rows, cols, format, padright; + int rows, cols, format; + unsigned int padright; + /* Number of bits of padding on the right of each row */ unsigned int row; - int bits_per_pixel; - int bits_per_item; - unsigned long red_mask, green_mask, blue_mask; + int bitsPerPixel; + int bitsPerItem; + struct compMask compMask; xelval maxval; enum visualclass visualclass; - enum byteorder byte_order, bit_order; - xel *colors; /* the color map */ + enum byteorder byteOrder, bitOrder; + xel * colors; /* the color map */ pixelReader pixelReader; pnm_init(&argc, argv); @@ -1210,24 +1294,23 @@ main(int argc, char *argv[]) { debug = cmdline.debug; verbose = cmdline.verbose; - if (cmdline.input_filename != NULL) - ifP = pm_openr(cmdline.input_filename); + if (cmdline.inputFilename != NULL) + ifP = pm_openr(cmdline.inputFilename); 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, + &colors, &bitsPerPixel, &bitsPerItem, + &compMask, &byteOrder, &bitOrder, 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); + format, bitsPerPixel, bitsPerItem, compMask, + byteOrder, bitOrder); - pixelReaderInit(&pixelReader, ifP, bits_per_pixel, bits_per_item, - byte_order, bit_order); + pixelReader_init(&pixelReader, ifP, bitsPerPixel, bitsPerItem, + byteOrder, bitOrder); pnm_writepnminit(stdout, cols, rows, maxval, format, 0); @@ -1235,9 +1318,11 @@ main(int argc, char *argv[]) { for (row = 0; row < rows; ++row) { convertRow(&pixelReader, stdout, - padright, cols, maxval, format, - red_mask, green_mask, blue_mask, colors, visualclass); + padright, cols, maxval, format, compMask, + colors, visualclass); } + + pixelReader_term(&pixelReader); pm_close(ifP); pm_close(stdout); @@ -1246,6 +1331,7 @@ main(int argc, char *argv[]) { } + /* 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. diff --git a/converter/pgm/pgmtopgm.c b/converter/pgm/pgmtopgm.c index c65e98e0..250bb4dc 100644 --- a/converter/pgm/pgmtopgm.c +++ b/converter/pgm/pgmtopgm.c @@ -19,7 +19,7 @@ main(int argc, char *argv[]) { int rows, cols; gray maxval; int row; - gray* grayrow; + gray * grayrow; pgm_init(&argc, argv); @@ -32,7 +32,7 @@ main(int argc, char *argv[]) { grayrow = pgm_allocrow(cols); - for (row = 0; row < rows; row++) { + for (row = 0; row < rows; ++row) { pgm_readpgmrow(stdin, grayrow, cols, maxval, format); pgm_writepgmrow(stdout, grayrow, cols, maxval, 0); } diff --git a/converter/ppm/picttoppm.c b/converter/ppm/picttoppm.c index 92eb04a4..62fb410a 100644 --- a/converter/ppm/picttoppm.c +++ b/converter/ppm/picttoppm.c @@ -94,16 +94,16 @@ struct rgbPlanes { word * blu; }; +struct canvas { + struct rgbPlanes planes; +}; + 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; @@ -142,10 +142,12 @@ static int ps_cent_x; static int ps_cent_y; static int ps_cent_set; +typedef void (drawFn)(struct canvas *, int); + struct opdef { const char* name; int len; - void (*impl) (int); + drawFn * impl; const char* description; }; @@ -178,13 +180,26 @@ struct raster { }; + static void allocateRaster(struct raster * const rasterP, unsigned int const width, unsigned int const height, unsigned int const bitsPerPixel) { - - unsigned int const allocWidth = (width + 15)/16 * 16; +/*---------------------------------------------------------------------------- + Allocate storage for a raster that can contain a 'width' x 'height' + pixel rectangle read from a PICT image with 'bitsPerPixel' bits + per pixel. + + Make the space large enough to round the number of pixels up to a + multiple of 16, because we've seen many images in which the PICT raster + does contain that much padding on the right. I don't know why; I could + understand a multiple of 8, since in 1 bpp image, the smallest unit + expressable in PICT is 8 pixels. But why 16? The images we saw came + from Adobe Illustrator 10 in March 2007, supplied by + Guillermo Gómez Valcárcel. +-----------------------------------------------------------------------------*/ + unsigned int const allocWidth = ROUNDUP(width, 16); if (width > UINT_MAX/4 - 16) pm_error("Width %u pixels too large for arithmetic", width); @@ -591,15 +606,23 @@ picComment(word const type, +static drawFn ShortComment; + static void -ShortComment(int const version) { +ShortComment(struct canvas * const canvasP, + int const version) { + picComment(read_word(), 0); } +static drawFn LongComment; + static void -LongComment(int const version) { +LongComment(struct canvas * const canvasP, + int const version) { + word type; type = read_word(); @@ -608,8 +631,12 @@ LongComment(int const version) { +static drawFn skip_poly_or_region; + static void -skip_poly_or_region(int const version) { +skip_poly_or_region(struct canvas * const canvasP, + int const version) { + stage = "skipping polygon or region"; skip(read_word() - 2); } @@ -687,10 +714,11 @@ load_fontdir(const char * const dirfile) { static void read_rect(struct Rect * const r) { - r->top = read_word(); - r->left = read_word(); + + r->top = read_word(); + r->left = read_word(); r->bottom = read_word(); - r->right = read_word(); + r->right = read_word(); } @@ -1424,6 +1452,7 @@ static void generalBlit(struct Rect const srcRect, struct Rect const srcBounds, struct raster const srcplane, + struct rgbPlanes const planes, int const pixSize, struct Rect const dstRect, struct Rect const dstBounds, @@ -1464,9 +1493,9 @@ generalBlit(struct Rect const srcRect, dstoff = (clipdst.top - dstBounds.top) * dstwid + (clipdst.left - dstBounds.left); - dst.red = red + dstoff; - dst.grn = green + dstoff; - dst.blu = blue + dstoff; + dst.red = planes.red + dstoff; + dst.grn = planes.grn + dstoff; + dst.blu = planes.blu + dstoff; /* get rid of Text mask mode bit, if (erroneously) set */ if ((mode & ~64) == 0) @@ -1493,6 +1522,7 @@ static int blit(struct Rect const srcRect, struct Rect const srcBounds, struct raster const srcplane, + struct canvas * const canvasP, int const pixSize, struct Rect const dstRect, struct Rect const dstBounds, @@ -1536,7 +1566,7 @@ blit(struct Rect const srcRect, retval = 0; } else { - generalBlit(srcRect, srcBounds, srcplane, pixSize, + generalBlit(srcRect, srcBounds, srcplane, canvasP->planes, pixSize, dstRect, dstBounds, dstwid, color_map, mode, clipsrc, clipdst); @@ -1555,17 +1585,15 @@ blit(struct Rect const srcRect, */ static void -allocPlanes(struct rgbPlanes * const planesP) { +allocPlanes(unsigned int const width, + unsigned int const height, + struct rgbPlanes * const planesP) { - struct rgbPlanes planes; - - rowlen = picFrame.right - picFrame.left; - collen = picFrame.bottom - picFrame.top; + unsigned int const planelen = width * height; - clip_rect = picFrame; + struct rgbPlanes planes; - planelen = rowlen * collen; - MALLOCARRAY(planes.red, planelen); + MALLOCARRAY(planes.red, planelen); MALLOCARRAY(planes.grn, planelen); MALLOCARRAY(planes.blu, planelen); if (planes.red == NULL || planes.grn == NULL || planes.blu == NULL) @@ -1577,14 +1605,6 @@ allocPlanes(struct rgbPlanes * const planesP) { memset(planes.blu, 255, planelen * sizeof(word)); *planesP = planes; - - /* Until we wean this program off of global variables, we have to - set these: - */ - - red = planes.red; - green = planes.grn; - blue = planes.blu; } @@ -1608,7 +1628,7 @@ compact(word const input) { static void -do_blits(struct rgbPlanes * const planesP) { +do_blits(struct canvas * const canvasP) { struct blit_info* bi; int srcwidth, dstwidth, srcheight, dstheight; @@ -1706,10 +1726,15 @@ do_blits(struct rgbPlanes * const planesP) { rectscale(&picFrame, xscale, yscale); } - allocPlanes(planesP); + rowlen = picFrame.right - picFrame.left; + collen = picFrame.bottom - picFrame.top; + + allocPlanes(rowlen, collen, &canvasP->planes); + + clip_rect = picFrame; for (bi = blit_list; bi; bi = bi->next) { - blit(bi->srcRect, bi->srcBounds, bi->srcplane, + blit(bi->srcRect, bi->srcBounds, bi->srcplane, canvasP, bi->pixSize, bi->dstRect, picFrame, rowlen, bi->color_map, @@ -1720,7 +1745,8 @@ do_blits(struct rgbPlanes * const planesP) { static void -outputPpm(struct rgbPlanes const planes) { +outputPpm(FILE * const ofP, + struct rgbPlanes const planes) { unsigned int width; unsigned int height; @@ -1736,7 +1762,7 @@ outputPpm(struct rgbPlanes const planes) { width = picFrame.right - picFrame.left; height = picFrame.bottom - picFrame.top; - ppm_writeppminit(stdout, width, height, PPM_MAXMAXVAL, 0); + ppm_writeppminit(ofP, width, height, PPM_MAXMAXVAL, 0); pixelrow = ppm_allocrow(width); srcCursor = 0; for (row = 0; row < height; ++row) { @@ -1749,9 +1775,8 @@ outputPpm(struct rgbPlanes const planes) { ); ++srcCursor; } - ppm_writeppmrow(stdout, pixelrow, width, PPM_MAXMAXVAL, 0); + ppm_writeppmrow(ofP, pixelrow, width, PPM_MAXMAXVAL, 0); } - pm_close(stdout); } @@ -1777,8 +1802,12 @@ get_op(int const version) { +static drawFn Clip; + static void -Clip(int const version) { +Clip(struct canvas * const canvasP, + int const version) { + word len; len = read_word(); @@ -1795,8 +1824,12 @@ Clip(int const version) { +static drawFn OpColor; + static void -OpColor(int const version) { +OpColor(struct canvas * const canvasP, + int const version) { + op_color.red = read_word(); op_color.grn = read_word(); op_color.blu = read_word(); @@ -1810,17 +1843,17 @@ 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->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->pmTable = read_long(); p->pmReserved = read_long(); if (verbose) { @@ -1828,6 +1861,8 @@ read_pixmap(struct pixMap * const p) { pm_message("pixelSize: %d", p->pixelSize); pm_message("cmpCount: %d", p->cmpCount); pm_message("cmpSize: %d", p->cmpSize); + if (verbose) + dumpRect("Bounds:", p->Bounds); } if (p->pixelType != 0) @@ -2103,6 +2138,9 @@ expandRun(unsigned char * const block, returned. -----------------------------------------------------------------------------*/ unsigned int const pkpixsize = bitsPerPixel == 16 ? 2 : 1; + /* The repetition unit size, in bytes. The run consists of this many + bytes of packed data repeated the specified number of times. + */ if (1 + pkpixsize > blockLimit) pm_error("PICT run block runs off the end of its line. " @@ -2118,14 +2156,28 @@ expandRun(unsigned char * const block, assert(block[0] & 0x80); /* It's a run */ if (verbose > 1) - pm_message("Block: run of %u pixels or plane samples", runLength); - + pm_message("Block: run of %u packed %u-byte units", + runLength, pkpixsize); + unpackBuf(&block[1], pkpixsize, bitsPerPixel, &bytePixels, &expandedByteCount); + + /* I assume in a legal PICT the run never has padding for the + case that the run is at the right edge of a row and the + remaining columns in the row don't fill whole bytes. + E.g. if there are 25 columns left in the row and 1 bit per + pixel, we won't see a run of 4 bytes and have to ignore the + last 7 pixels. Instead, we'll see a run of 3 bytes + followed by a non-run block for the remaining pixel. + + That is what I saw in a test image. + */ - if (expandedByteCount > expandedSize) + if (expandedByteCount * runLength > expandedSize) pm_error("Invalid PICT image. It contains a row with more pixels " - "than the width of the image"); + "than the width of the rectangle containing it, " + "even padded up to a " + "multiple of 16 pixels. Use -verbose to see details."); outputCursor = 0; for (i = 0; i < runLength; ++i) { @@ -2150,9 +2202,23 @@ copyPixelGroup(unsigned char * const block, unsigned int * const rasterBytesGeneratedP) { /*---------------------------------------------------------------------------- Copy a group of pixels (the data says, "take the following N pixels"). + + Copy them (unpacked) from block block[] to dest[]. + + block[] self-describes its length. Return that length as + *blockLengthP. + + block[] contains at most 'blockLimit' valid array elements, so if + the length information in block[] indicates the block is larger + than that, the block is corrupt. + + Return the number of pixels placed in dest[] as *rasterBytesGeneratedP. + + The output array dest[] has 'destSize' elements of space. Ignore + any pixels on the right that won't fit in that. -----------------------------------------------------------------------------*/ - unsigned int const pkpixsize = bitsPerPixel == 16 ? 2 : 1; - unsigned int const groupLen = block[0] + 1; + 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) @@ -2163,22 +2229,35 @@ copyPixelGroup(unsigned char * const block, unsigned int i; unsigned char * bytePixels; /* Points to static storage */ unsigned int bytePixelLen; + unsigned int rasterBytesGenerated; 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); + pm_message("Block: %u explicit packed %u-byte units", + groupLen, pkpixsize); unpackBuf(&block[1], groupLen * pkpixsize, bitsPerPixel, &bytePixels, &bytePixelLen); - - for (i = 0; i < MIN(bytePixelLen, destSize); ++i) + + /* It is normal for the above to return more pixels than there + are left in the row, because of padding. E.g. there is one + pixel left in the row, at one bit per pixel. But a block + contains full bytes, so it must contain at least 8 pixels. + 7 of them are padding, which we should ignore. + + BUT: I saw an image in which the block had _two_ data bytes + (16 pixels) when only 1 pixel remained in the row. I don't + understand why, but ignoring the 15 extra seemed to work. + */ + rasterBytesGenerated = MIN(bytePixelLen, destSize); + + for (i = 0; i < rasterBytesGenerated; ++i) dest[i] = bytePixels[i]; *blockLengthP = blockLength; - *rasterBytesGeneratedP = MIN(bytePixelLen, destSize); + *rasterBytesGeneratedP = rasterBytesGenerated; } } @@ -2222,12 +2301,57 @@ static unsigned int const maxPixelBytesPerBlock = 1024; static void +interpretCompressedLine(unsigned char * const linebuf, + unsigned int const linelen, + unsigned char * const rowRaster, + unsigned int const rowSize, + unsigned int const bitsPerPixel) { +/*---------------------------------------------------------------------------- + linebuf[] contains 'linelen' bytes from the PICT image that represents + one row of the image, in compressed format. Return the + uncompressed pixels of that row as rowRaster[]. + + rowRaster[] has 'rowSize' bytes of space. Caller ensures that + linebuf[] does not contain more pixels than that, unless the PICT + image from which it comes is corrupt. +-----------------------------------------------------------------------------*/ + unsigned int lineCursor; + /* Cursor into linebuf[] -- the compressed data */ + unsigned int rasterCursor; + /* Cursor into rowRaster[] -- the uncompressed data */ + + for (lineCursor = 0, rasterCursor = 0; lineCursor < linelen; ) { + unsigned int blockLength, rasterBytesGenerated; + + assert(lineCursor <= linelen); + + if (verbose > 1) + pm_message("At Byte %u of line, Column %u of row", + lineCursor, rasterCursor); + + interpretOneRasterBlock( + &linebuf[lineCursor], linelen - lineCursor, + bitsPerPixel, + &rowRaster[rasterCursor], rowSize - rasterCursor, + &blockLength, &rasterBytesGenerated); + + lineCursor += blockLength; + rasterCursor += rasterBytesGenerated; + assert(rasterCursor <= rowSize); + } + if (verbose > 1) + pm_message("Got %u pixels for row", rasterCursor); +} + + + +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'. + Read the raster 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. @@ -2240,6 +2364,9 @@ unpackCompressedBits(FILE * const ifP, *boundsP describes the rectangle. -----------------------------------------------------------------------------*/ unsigned int const llsize = rowBytes > 200 ? 2 : 1; + /* Width in bytes of the field at the beginning of a line that tells + how long (in bytes) the line is. + */ unsigned int rowOfRect; unsigned char * linebuf; unsigned int linebufSize; @@ -2253,8 +2380,6 @@ unpackCompressedBits(FILE * const ifP, unsigned char * const rowRaster = &raster.bytes[rowOfRect * raster.rowSize]; unsigned int linelen; - unsigned int lineCursor; - unsigned int rasterCursor; if (llsize == 2) linelen = read_word(); @@ -2262,7 +2387,7 @@ unpackCompressedBits(FILE * const ifP, linelen = read_byte(); if (verbose > 1) - pm_message("linelen: %u", linelen); + pm_message("Row %u: %u-byte line", rowOfRect, linelen); if (linelen > linebufSize) { linebufSize = linelen; @@ -2272,23 +2397,8 @@ unpackCompressedBits(FILE * const ifP, } 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); + interpretCompressedLine(linebuf, linelen, rowRaster, raster.rowSize, + bitsPerPixel); } free(linebuf); } @@ -2390,21 +2500,39 @@ read_pattern(void) { /* these 3 do nothing but skip over their data! */ +static drawFn BkPixPat; + static void -BkPixPat(int const version) { +BkPixPat(struct canvas * const canvasP, + int const version) { + read_pattern(); } + + +static drawFn PnPixPat; + static void -PnPixPat(int const version) { +PnPixPat(struct canvas * const canvasP, + int const version) { + read_pattern(); } + + +static drawFn FillPixPat; + static void -FillPixPat(int const version) { +FillPixPat(struct canvas * const canvasP, + int const version) { + read_pattern(); } + + static void read_8x8_pattern(struct Pattern * const pat) { unsigned char buf[8]; @@ -2428,29 +2556,45 @@ read_8x8_pattern(struct Pattern * const pat) { +static drawFn BkPat; + static void -BkPat(int const version) { +BkPat(struct canvas * const canvasP, + int const version) { + read_8x8_pattern(&bkpat); } +static drawFn PnPat; + static void -PnPat(int const version) { +PnPat(struct canvas * const canvasP, + int const version) { + read_8x8_pattern(&pen_pat); } +static drawFn FillPat; + static void -FillPat(int const version) { +FillPat(struct canvas * const canvasP, + int const version) { + read_8x8_pattern(&fillpat); } +static drawFn PnSize; + static void -PnSize(int const version) { +PnSize(struct canvas * const canvasP, + int const version) { + pen_height = read_word(); pen_width = read_word(); if (verbose) @@ -2459,8 +2603,11 @@ PnSize(int const version) { +static drawFn PnSize; + static void -PnMode(int const version) { +PnMode(struct canvas * const canvasP, + int const version) { pen_mode = read_word(); @@ -2484,8 +2631,12 @@ read_rgb(struct RGBColor * const rgb) { +static drawFn RGBFgCol; + static void -RGBFgCol(int const v) { +RGBFgCol(struct canvas * const canvasP, + int const version) { + read_rgb(&foreground); if (verbose) pm_message("foreground now [%d,%d,%d]", @@ -2494,8 +2645,12 @@ RGBFgCol(int const v) { +static drawFn RGBBkCol; + static void -RGBBkCol(int const v) { +RGBBkCol(struct canvas * const canvasP, + int const version) { + read_rgb(&background); if (verbose) pm_message("background now [%d,%d,%d]", @@ -2507,34 +2662,37 @@ RGBBkCol(int const v) { #define PIXEL_INDEX(x,y) ((y) - picFrame.top) * rowlen + (x) - picFrame.left static void -draw_pixel(int const x, - int const y, +draw_pixel(struct canvas * const canvasP, + int const x, + int const y, struct RGBColor * const clr, - transfer_func trf) { - - int i; - struct RGBColor dst; + transfer_func trf) { if (x < clip_rect.left || x >= clip_rect.right || - y < clip_rect.top || y >= clip_rect.bottom) - { - return; - } + y < clip_rect.top || y >= clip_rect.bottom) { + } else { + unsigned int const i = PIXEL_INDEX(x, y); + + struct RGBColor dst; + + dst.red = canvasP->planes.red[i]; + dst.grn = canvasP->planes.grn[i]; + dst.blu = canvasP->planes.blu[i]; + + (*trf)(clr, &dst); - 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; + canvasP->planes.red[i] = dst.red; + canvasP->planes.grn[i] = dst.grn; + canvasP->planes.blu[i] = dst.blu; + } } static void -draw_pen_rect(struct Rect * const r) { +draw_pen_rect(struct canvas * const canvasP, + struct Rect * const r) { + int const rowadd = rowlen - (r->right - r->left); int i; @@ -2545,16 +2703,18 @@ draw_pen_rect(struct Rect * const r) { 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]; + dst.red = canvasP->planes.red[i]; + dst.grn = canvasP->planes.grn[i]; + dst.blu = canvasP->planes.blu[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; + + canvasP->planes.red[i] = dst.red; + canvasP->planes.grn[i] = dst.grn; + canvasP->planes.blu[i] = dst.blu; i++; } @@ -2565,8 +2725,10 @@ draw_pen_rect(struct Rect * const r) { static void -draw_pen(int const x, - int const y) { +draw_pen(struct canvas * const canvasP, + int const x, + int const y) { + struct Rect penrect; penrect.left = x; @@ -2576,7 +2738,7 @@ draw_pen(int const x, rectinter(penrect, clip_rect, &penrect); - draw_pen_rect(&penrect); + draw_pen_rect(canvasP, &penrect); } /* @@ -2593,10 +2755,12 @@ draw_pen(int const x, * Paul Heckbert 3 Sep 85 */ static void -scan_line(short const x1, - short const y1, - short const x2, - short const y2) { +scan_line(struct canvas * const canvasP, + 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)) { @@ -2609,7 +2773,7 @@ scan_line(short const x1, if (ax>ay) { /* x dominant */ d = ay-(ax>>1); for (;;) { - draw_pen(x, y); + draw_pen(canvasP, x, y); if (x==x2) return; if ((x > rowlen) && (sx > 0)) return; if (d>=0) { @@ -2623,7 +2787,7 @@ scan_line(short const x1, else { /* y dominant */ d = ax-(ay>>1); for (;;) { - draw_pen(x, y); + draw_pen(canvasP, x, y); if (y==y2) return; if ((y > collen) && (sy > 0)) return; if (d>=0) { @@ -2639,70 +2803,88 @@ scan_line(short const x1, +static drawFn Line; + static void -Line(int const v) { +Line(struct canvas * const canvasP, + int const version) { + struct Point p1; read_point(&p1); read_point(¤t); 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); + scan_line(canvasP, p1.x,p1.y,current.x,current.y); } +static drawFn LineFrom; + 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); +LineFrom(struct canvas * const canvasP, + int const version) { - if (!fullres) - scan_line(current.x,current.y,p1.x,p1.y); + 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(canvasP, current.x, current.y, p1.x, p1.y); - current.x = p1.x; - current.y = p1.y; + current.x = p1.x; + current.y = p1.y; } +static drawFn ShortLine; + static void -ShortLine(int const v) { - struct Point p1; - read_point(&p1); - read_short_point(¤t); - 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; +ShortLine(struct canvas * const canvasP, + int const version) { - if (!fullres) - scan_line(p1.x,p1.y,current.x,current.y); + struct Point p1; + read_point(&p1); + read_short_point(¤t); + 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(canvasP, p1.x, p1.y, current.x, current.y); } +static drawFn ShortLineFrom; + 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; +ShortLineFrom(struct canvas * const canvasP, + int const version) { + + 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(canvasP, current.x, current.y, p1.x, p1.y); + current.x = p1.x; + current.y = p1.y; } + + static void -do_paintRect(struct Rect const prect) { +do_paintRect(struct canvas * const canvasP, + struct Rect const prect) { + struct Rect rect; if (fullres) @@ -2713,28 +2895,37 @@ do_paintRect(struct Rect const prect) { rectinter(clip_rect, prect, &rect); - draw_pen_rect(&rect); + draw_pen_rect(canvasP, &rect); } +static drawFn paintRect; + static void -paintRect(int const v) { +paintRect(struct canvas * const canvasP, + int const version) { + read_rect(&cur_rect); - do_paintRect(cur_rect); + do_paintRect(canvasP, cur_rect); } +static drawFn paintSameRect; + static void -paintSameRect(int const v) { - do_paintRect(cur_rect); +paintSameRect(struct canvas * const canvasP, + int const version) { + + do_paintRect(canvasP, cur_rect); } static void -do_frameRect(struct Rect const rect) { +do_frameRect(struct canvas * const canvasP, + struct Rect const rect) { int x, y; if (fullres) @@ -2747,29 +2938,37 @@ do_frameRect(struct Rect const rect) { 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); + draw_pen(canvasP, x, rect.top); + draw_pen(canvasP, 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); + draw_pen(canvasP, rect.left, y); + draw_pen(canvasP, rect.right - pen_width, y); } } +static drawFn frameRect; + static void -frameRect(int const v) { +frameRect(struct canvas * const canvasP, + int const version) { + read_rect(&cur_rect); - do_frameRect(cur_rect); + do_frameRect(canvasP, cur_rect); } +static drawFn frameSameRect; + static void -frameSameRect(int const v) { - do_frameRect(cur_rect); +frameSameRect(struct canvas * const canvasP, + int const version) { + + do_frameRect(canvasP, cur_rect); } @@ -2810,8 +3009,10 @@ poly_sort(int const sort_index, struct Point points[]) { /* Watch out for the lack of error checking in the next two functions ... */ static void -scan_poly(int const np, - struct Point pts[]) { +scan_poly(struct canvas * const canvasP, + 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; @@ -2864,7 +3065,7 @@ scan_poly(int const np, scan_index++; } px += sdx; - draw_pen(px, py); + draw_pen(canvasP, px, py); } } else @@ -2880,7 +3081,7 @@ scan_poly(int const np, old_sdy = sdy; if (sdy != 0) scan_index--; } - draw_pen(px,py); + draw_pen(canvasP, px,py); coord[scan_index].x = px; coord[scan_index].y = py; scan_index++; @@ -2900,7 +3101,7 @@ scan_poly(int const np, 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); + draw_pen(canvasP, j, coord[i].y); toggle = 1; } else @@ -2909,9 +3110,13 @@ scan_poly(int const np, } + +static drawFn paintPoly; static void -paintPoly(int const v) { +paintPoly(struct canvas * const canvasP, + int const version) { + struct Rect bb; struct Point pts[100]; int i, np = (read_word() - 10) >> 2; @@ -2922,13 +3127,17 @@ paintPoly(int const v) { /* scan convert poly ... */ if (!fullres) - scan_poly(np, pts); + scan_poly(canvasP, np, pts); } +static drawFn PnLocHFrac; + static void -PnLocHFrac(int const version) { +PnLocHFrac(struct canvas * const canvasP, + int const version) { + word frac = read_word(); if (verbose) @@ -2937,8 +3146,12 @@ PnLocHFrac(int const version) { +static drawFn TxMode; + static void -TxMode(int const version) { +TxMode(struct canvas * const canvasP, + int const version) { + text_mode = read_word(); if (text_mode >= 8 && text_mode < 15) @@ -2953,8 +3166,12 @@ TxMode(int const version) { +static drawFn TxFont; + static void -TxFont(int const version) { +TxFont(struct canvas * const canvasP, + int const version) { + text_font = read_word(); if (verbose) pm_message("text font %s", const_name(font_name, text_font)); @@ -2962,8 +3179,12 @@ TxFont(int const version) { +static drawFn TxFace; + static void -TxFace(int const version) { +TxFace(struct canvas * const canvasP, + int const version) { + text_face = read_byte(); if (verbose) pm_message("text face %d", text_face); @@ -2971,8 +3192,12 @@ TxFace(int const version) { +static drawFn TxSize; + static void -TxSize(int const version) { +TxSize(struct canvas * const canvasP, + int const version) { + text_size = read_word(); if (verbose) pm_message("text size %d", text_size); @@ -3062,8 +3287,10 @@ rotate(int * const x, static void -do_ps_text(word const tx, - word const ty) { +do_ps_text(struct canvas * const canvasP, + word const tx, + word const ty) { + int len, width, i, w, h, x, y, rx, ry, o; byte str[256], ch; struct glyph* glyph; @@ -3114,9 +3341,9 @@ do_ps_text(word const tx, { o = PIXEL_INDEX(rx, ry); if (glyph->bmap[h * glyph->width + w]) { - red[o] = foreground.red; - green[o] = foreground.grn; - blue[o] = foreground.blu; + canvasP->planes.red[o] = foreground.red; + canvasP->planes.grn[o] = foreground.grn; + canvasP->planes.blu[o] = foreground.blu; } } } @@ -3129,8 +3356,10 @@ do_ps_text(word const tx, static void -do_text(word const startx, - word const starty) { +do_text(struct canvas * const canvasP, + word const startx, + word const starty) { + if (fullres) skip_text(); else { @@ -3138,7 +3367,7 @@ do_text(word const startx, tfont = pbm_defaultfont("bdf"); if (ps_text) - do_ps_text(startx, starty); + do_ps_text(canvasP, startx, starty); else { int len; word x, y; @@ -3158,7 +3387,8 @@ do_text(word const startx, struct RGBColor * const colorP = glyph->bmap[h * glyph->width + w] ? &black : &white; - draw_pixel(x + w + glyph->x, dy, colorP, text_trf); + draw_pixel(canvasP, + x + w + glyph->x, dy, colorP, text_trf); } } x += glyph->xadd; @@ -3172,34 +3402,50 @@ do_text(word const startx, +static drawFn LongText; + static void -LongText(int const version) { +LongText(struct canvas * const canvasP, + int const version) { + struct Point p; read_point(&p); - do_text(p.x, p.y); + + do_text(canvasP, p.x, p.y); } +static drawFn DHText; + static void -DHText(int const version) { +DHText(struct canvas * const canvasP, + int const version) { + current.x += read_byte(); - do_text(current.x, current.y); + do_text(canvasP, current.x, current.y); } +static drawFn DVText; + static void -DVText(int const version) { +DVText(struct canvas * const canvasP, + int const version) { + current.y += read_byte(); - do_text(current.x, current.y); + do_text(canvasP, current.x, current.y); } +static drawFn DHDVText; + static void -DHDVText(int const version) { +DHDVText(struct canvas * const canvasP, + int const version) { byte dh, dv; dh = read_byte(); @@ -3210,7 +3456,7 @@ DHDVText(int const version) { current.x += dh; current.y += dv; - do_text(current.x, current.y); + do_text(canvasP, current.x, current.y); } @@ -3220,8 +3466,9 @@ DHDVText(int const version) { */ static void -directBits(unsigned int const pictVersion, - bool const skipRegion) { +directBits(struct canvas * const canvasP, + unsigned int const pictVersion, + bool const skipRegion) { struct pixMap p; struct Rect srcRect; @@ -3261,11 +3508,11 @@ directBits(unsigned int const pictVersion, pm_message("transfer mode = %s", const_name(transfer_name, mode)); if (skipRegion) - skip_poly_or_region(pictVersion); + skip_poly_or_region(canvasP, pictVersion); unpackbits(ifp, &p.Bounds, 0, p.pixelSize, &raster); - blit(srcRect, p.Bounds, raster, p.pixelSize, + blit(srcRect, p.Bounds, raster, canvasP, p.pixelSize, dstRect, picFrame, rowlen, NULL, mode); freeRaster(raster); @@ -3276,26 +3523,33 @@ directBits(unsigned int const pictVersion, #define SKIP_REGION_TRUE TRUE #define SKIP_REGION_FALSE FALSE +static drawFn DirectBitsRect; + static void -DirectBitsRect(int const version) { +DirectBitsRect(struct canvas * const canvasP, + int const version) { - directBits(version, SKIP_REGION_FALSE); + directBits(canvasP, version, SKIP_REGION_FALSE); } +static drawFn DirectBitsRgn; + static void -DirectBitsRgn(int const version) { +DirectBitsRgn(struct canvas * const canvasP, + int const version) { - directBits(version, SKIP_REGION_TRUE); + directBits(canvasP, version, SKIP_REGION_TRUE); } static void -do_pixmap(int const version, - word const rowBytes, - int const is_region) { +do_pixmap(struct canvas * const canvasP, + int const version, + word const rowBytes, + int const is_region) { /*---------------------------------------------------------------------------- Do a paletted image. -----------------------------------------------------------------------------*/ @@ -3331,13 +3585,13 @@ do_pixmap(int const version, pm_message("transfer mode = %s", const_name(transfer_name, mode)); if (is_region) - skip_poly_or_region(version); + skip_poly_or_region(canvasP, version); stage = "unpacking rectangle"; unpackbits(ifp, &p.Bounds, rowBytes, p.pixelSize, &raster); - blit(srcRect, p.Bounds, raster, 8, + blit(srcRect, p.Bounds, raster, canvasP, 8, dstRect, picFrame, rowlen, color_table, mode); free(color_table); @@ -3347,17 +3601,24 @@ do_pixmap(int const version, static void -do_bitmap(int const version, - int const rowBytes, - int const is_region) { +do_bitmap(FILE * const ifP, + struct canvas * const canvasP, + 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. + + Read the raster from file 'ifP'. -----------------------------------------------------------------------------*/ struct Rect Bounds; struct Rect srcRect; struct Rect dstRect; word mode; struct raster raster; + /* This raster contains padding on the right to make a multiple + of 16 pixels per row. + */ static struct RGBColor color_table[] = { {65535L, 65535L, 65535L}, {0, 0, 0} }; @@ -3369,13 +3630,13 @@ do_bitmap(int const version, pm_message("transfer mode = %s", const_name(transfer_name, mode)); if (is_region) - skip_poly_or_region(version); + skip_poly_or_region(canvasP, version); stage = "unpacking rectangle"; - unpackbits(ifp, &Bounds, rowBytes, 1, &raster); + unpackbits(ifP, &Bounds, rowBytes, 1, &raster); - blit(srcRect, Bounds, raster, 8, + blit(srcRect, Bounds, raster, canvasP, 8, dstRect, picFrame, rowlen, color_table, mode); freeRaster(raster); @@ -3383,8 +3644,11 @@ do_bitmap(int const version, +static drawFn BitsRect; + static void -BitsRect(int const version) { +BitsRect(struct canvas * const canvasP, + int const version) { word rowBytesWord; bool pixMap; @@ -3396,16 +3660,19 @@ BitsRect(int const version) { interpretRowBytesWord(rowBytesWord, &pixMap, &rowBytes); if (pixMap) - do_pixmap(version, rowBytes, 0); + do_pixmap(canvasP, version, rowBytes, 0); else - do_bitmap(version, rowBytes, 0); + do_bitmap(ifp, canvasP, version, rowBytes, 0); } -static void -BitsRegion(int const version) { +static drawFn BitsRegion; +static void +BitsRegion(struct canvas * const canvasP, + int const version) { + word rowBytesWord; bool pixMap; unsigned int rowBytes; @@ -3416,9 +3683,9 @@ BitsRegion(int const version) { interpretRowBytesWord(rowBytesWord, &pixMap, &rowBytes); if (pixMap) - do_pixmap(version, rowBytes, 1); + do_pixmap(canvasP, version, rowBytes, 1); else - do_bitmap(version, rowBytes, 1); + do_bitmap(ifp, canvasP, version, rowBytes, 1); } @@ -3601,14 +3868,15 @@ static struct opdef const optable[] = { static void -interpret_pict(void) { +interpret_pict(FILE * const ofP) { + byte ch; word picSize; word opcode; word len; unsigned int version; int i; - struct rgbPlanes planes; + struct canvas canvas; for (i = 0; i < 64; i++) pen_pat.pix[i] = bkpat.pix[i] = fillpat.pix[i] = 1; @@ -3634,8 +3902,14 @@ interpret_pict(void) { picFrame.bottom - picFrame.top); } - if (!fullres) - allocPlanes(&planes); + if (!fullres) { + rowlen = picFrame.right - picFrame.left; + collen = picFrame.bottom - picFrame.top; + + allocPlanes(rowlen, collen, &canvas.planes); + + clip_rect = picFrame; + } while ((ch = read_byte()) == 0) ; @@ -3672,7 +3946,7 @@ interpret_pict(void) { } if (optable[opcode].impl != NULL) - (*optable[opcode].impl)(version); + (*optable[opcode].impl)(&canvas, version); else if (optable[opcode].len >= 0) skip(optable[opcode].len); else switch (optable[opcode].len) { @@ -3721,11 +3995,11 @@ interpret_pict(void) { } if (fullres) - do_blits(&planes); + do_blits(&canvas); - outputPpm(planes); + outputPpm(ofP, canvas.planes); - freePlanes(planes); + freePlanes(canvas.planes); } @@ -3782,7 +4056,9 @@ main(int argc, char * argv[]) { skip(512); } - interpret_pict(); + interpret_pict(stdout); + + pm_close(stdout); return 0; } diff --git a/doc/HISTORY b/doc/HISTORY index 5bb4d539..5e905ef6 100644 --- a/doc/HISTORY +++ b/doc/HISTORY @@ -4,62 +4,104 @@ Netpbm. CHANGE HISTORY -------------- -not yet BJH Release 10.37.06 +07.03.30 BJH Release 10.38.0 + + Add pamfixtrunc. - ppmtogif: handle case that map file and input file are not - the same depth, as was the case with the original ppmtogif. - Run the input through pnmremap. + pamtogif: Add -aspect. Thanks + Prophet of the Way <afu@wta.att.ne.jp>. - picttoppm: Fix wild memory reference in all use cases. + pamditherbw: Add -atkinson. - picttoppm: Fix for multi-pixel-per-byte image in which - it says the image has a row that is too long (because of - padding). + pammixinterlace: Add -filter and fir and ffmpeg filters. + Thanks Bruce Guenter <bruce@untroubled.org>. - configure: fix choice of default library suffix. + pammixinterlace: Add -adaptive. + Thanks Bruce Guenter <bruce@untroubled.org>. -07.03.23 BJH Release 10.37.05 + pambackground: recognize mid-row background. - pnmremap: fix incorrect output with map file deeper than - input file. + ppm3d: Change default offset to zero columns. - Pnmtopclxl: fix -feeder, -outtray options. Thanks - "Eric K. Olson" <olson@mauicomputing.com>. + ppm3d: Add -color option. -07.03.09 BJH Release 10.37.04 + ppm3d: Add -offset option as alternative to offset argument. - pamtogif: fix crash with -mapfile. + jpegtopnm: Add -repair option. - libnetpbm: fix crash with PBM images < 8 columns on MMX/SSE - machine. + giftopnm: Add -repair option. + + xwdtopnm: use pm_drain() to catch some format + misinterpretations. - pamtoxvmini: fix maxval != 255 bugs. + pamtogif: Speed up for monochrome images. Thanks + Prophet of the Way <afu@wta.att.ne.jp>. + + pamtogif: Speed up for small images by using smaller + hash table (so smaller memory footprint). Thanks + Prophet of the Way <afu@wta.att.ne.jp>. + libnetpbm: add pm_drain(). + libnetpbm: shhopt: reject signed number as value for OPT_UINT option. -07.02.21 BJH Release 10.37.03 + libnetpbm: in the "no such option" error message, say what + the valid options are. - pamtoxvmini: fix crash. + libnetpbm: Embellish "bad magic number" error message. - pamtogif: fail properly if image to wide or high for GIF. + pnmremap: fix incorrect output with map file deeper than + input file. + + xwdtopnm: fix right edge padding for 24 bit per pixel, + 32 bit per item images. -07.01.15 BJH Release 10.37.02 + xwdtopnm: update assumptions about format for direct color + images to match an image we found. + + pnmtopclxl: fix -feeder, -outtray options. Thanks + "Eric K. Olson" <olson@mauicomputing.com>. + + picttoppm: Fix wild memory reference in all use cases. + + picttoppm: Fix for multi-pixel-per-byte image in which + it says the image has a row that is too long (because of + padding). + + pamtogif: fix crash with -mapfile. + + ppmtogif: handle case that map file and input file are not + the same depth, as was the case with the original ppmtogif. + Run the input through pnmremap. + + pamtoxvmini: fix bug: produces garbage when maxval is + not 255. + + pamtoxvmini: fix crash. libnetpbm: fix buffer overrun with PBM images < 8 columns. + libnetpbm: fix crash with PBM images < 8 columns on MMX/SSE + machine. + + pamtogif: fail properly if image to wide or high for GIF. + ppmdraw: fix crash with use of freed storage. Thanks John Walker <kelvin@fourmilab.ch>. + libnetpbm: fix crash with PAM read as PNM. + installnetpbm: use 2-argument open() for old Perl. -06.12.31 BJH Release 10.37.01 + configure: fix choice of default library suffix. - libnetpbm: fix crash with PAM read as PNM. + configure: default to 'none' for Svgalib if it doesn't appear + to be installed. -06.12.31 BJH Release 10.37.00 +06.12.31 BJH Release 10.37.0 - add pambackground. But doesn't find mid-row background yet. + Add pambackground. But doesn't find mid-row background yet. pnmcrop, pamtopnm: work on multi-image stream. Thanks Erik Auerswald <auerswal@unix-ag.uni-kl.de>. @@ -138,7 +180,7 @@ not yet BJH Release 10.37.06 Build: Fix some compile failures with Irix IDO compiler. - Build: fix Darwin build. + Build: fix Darwin (Mac OS X) build. Configure: fix default for X library location. @@ -3033,41 +3075,41 @@ 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. +libpbm1.c strstr() added to libpbm1.c. +libpbm5.c BDF font functions added. +pbmtext Ability to use BDF fonts added. +pbmto4425 New filter. +pbmtoln03 Command line parsing changed to Pbmplus standard. PGM -pgmnoise New filter. +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. +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. +pnmtoddif New filter. +pnmhistmap New facility. +pnmtops New option (-nocenter) added. Functional changes to Netpbm since 7 December 1993. @@ -3075,25 +3117,25 @@ 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. +asciitopgm New filter. +fitstopgm Replaced by fitstopnm. +pgmtofits Replaced by pnmtofits. +pgmtopbm Upgraded. +pgmkernel New filter. PPM -ppmchange Upgraded. -xvminitoppm New filter. +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. +pnmalias New filter. +pnmtofits Replacement for pgmtofits. +fitstopnm Replacement for fitstopgm. +pnmtosgi New filter. +sgitopnm New filter. +pstopnm New filter. @@ -3104,126 +3146,126 @@ The following is new in Netpbm (compared to Pbmplus): PBM -pbmtext BDF font support added. +pbmtext Ability to use BDF fonts added. -pbmto4425 Display on an AT&T 4425 Ascii terminal. +pbmto4425 Display on an AT&T 4425 Ascii terminal. -pbmtoascii A new improved version. +pbmtoascii A new improved version. -pbmtoln03 Convert to DEC LN03+. +pbmtoln03 Convert to DEC LN03+. -pbmtolps Fast PostScript creator. +pbmtolps Fast PostScript creator. -pbmtopk Conversion to/from a packed (PK) format font. +pbmtopk Conversion to/from a packed (PK) format font. pktopbm -pbmclean Flip isolated pixels. +pbmclean Flip isolated pixels. -pbmpscale Enlarge pbm image with edge smoothing. +pbmpscale Enlarge pbm image with edge smoothing. PGM -asciitopgm Convert an ascii image into pgm. +asciitopgm Convert an ascii image into pgm. -pbmtopgm Convert pbm to pgm by averaging areas. +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. +rawtopgm Handles input files without specification of the file size, + assuming the input image is quadratic. It also has a + -tb (top bottom flip) option. -bioradtopgm Conversion utility for files created by Biorad confocal - microscopes. +bioradtopgm Conversion utility for files created by Biorad confocal + microscopes. -spottopgm Convert SPOT satellite images to pgm. +spottopgm Convert SPOT satellite images to pgm. -pgmkernel Generate a convolution kernel. +pgmkernel Generate a convolution kernel. -pgmnoise Create a pgm file with random pixels. +pgmnoise Create a pgm file with random pixels. PPM -bmptoppm Conversion to/from windows bitmap format. +bmptoppm Conversion to/from windows bitmap format. ppmtobmp -ppmtogif Updated version. -giftoppm Removed (see giftopnm). +ppmtogif Updated version. +giftoppm Removed (see giftopnm). -ppmtoilbm Updated version. +ppmtoilbm Updated version. ilbmtoppm -picttoppm Updated version. +picttoppm Updated version. ppmtopict -ppmtoxpm Updated version, which supports xpm version 3. +ppmtoxpm Updated version, which understands xpm version 3. xpmtoppm -ppmtomap Extract all colors from a ppm file. +ppmtomap Extract all colors from a ppm file. -ppmtomitsu Convert to Mitsubishi S340-10 printer format. +ppmtomitsu Convert to Mitsubishi S340-10 printer format. -xvminitoppm Convert an XV thumbnail picture to ppm. +xvminitoppm Convert an XV thumbnail picture to ppm. -ppmtoyuvsplit Conversion to/from YUV triplets. (MPEG / JPEG). +ppmtoyuvsplit Conversion to/from YUV triplets. (MPEG / JPEG). yuvsplittoppm -ppm3d Create a red/blue stereo image. +ppm3d Create a red/blue stereo image. -ppmbrighten Change image saturation and value on an HSV map. +ppmbrighten Change image saturation and value on an HSV map. -ppmchange Change all pixels of one color to another in a portable pixmap +ppmchange Change all pixels of one color to another in a portable pixmap -ppmdim Dim a ppm file down to total blackness. +ppmdim Dim a ppm file down to total blackness. -ppmdist Simplistic grayscale assignment for machine generated - color images. +ppmdist Simplistic grayscale assignment for machine generated + color images. -ppmflash Brighten a picture up to complete white-out +ppmflash Brighten a picture up to complete white-out -ppmmix Blend together two portable pixmaps. +ppmmix Blend together two portable pixmaps. -ppmnorm Normalize the contrast in a portable pixmap. +ppmnorm Normalize the contrast in a portable pixmap. -ppmntsc Make a portable pixmap look like taken from an American TV. +ppmntsc Make a portable pixmap look like taken from an American TV. -ppmqvga Eight plane quantization. +ppmqvga Eight plane quantization. -ppmshift Shift lines of a portable pixmap left or right by a random amount. +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. +ppmspread Displace a portable pixmap's pixels by a random amount. -ppmtopjxl Convert a ppm file into an HP PaintJet XL PCL file. +ppmtopjxl Convert a ppm file into an HP PaintJet XL PCL file. PNM -pnmtops New option (-nocenter) added. +pnmtops New option (-nocenter) added. -pnmtofits Replacement for pgmtofits/fitstopgm +pnmtofits Replacement for pgmtofits/fitstopgm fitstopnm -pnmtosgi Conversion to/from sgi image format. +pnmtosgi Conversion to/from sgi image format. sgitopnm -pnmtosir Conversion to/from Solitaire image recorder format. +pnmtosir Conversion to/from Solitaire image recorder format. sirtopnm -giftopnm Replaces giftoppm. Examines the input image and produces - a pbm, pgm, or ppm output. +giftopnm Replaces giftoppm. Examines the input image and produces + a pbm, pgm, or ppm output. -pstopnm Convert PostScript to pnm. Requires Ghostscript. +pstopnm Convert PostScript to pnm. Requires Ghostscript. -zeisstopnm Conversion utility for files created by Zeiss confocal - microscopes (the old standard). +zeisstopnm Conversion utility for files created by Zeiss confocal + microscopes (the old standard). -pnmalias Anti aliasing filter. +pnmalias Anti aliasing filter. -pnmcomp Composite two portable anymaps together. +pnmcomp Composite two portable anymaps together. -pnmcrop New options added. +pnmcrop New options added. -pnmpad Add borders to anymap. +pnmpad Add borders to anymap. LIBTIFF @@ -3307,7 +3349,7 @@ Changes during the extended beta test period, starting on 15jan91: 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 recognition of X11R5's new color specifiers rgb: and rgbi:. Added pgmtexture. (James Darrell McCauley) Added ppmtopj, pjtoppm, and ppmdither. (Christos Zoulas) Added ppmtotga. (Mark Shand) @@ -3315,7 +3357,7 @@ Changes during the extended beta test period, starting on 15jan91: 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) + Added recognition of 24-bit images to ilbmtoppm. (Mark Thompson) Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89: @@ -3356,7 +3398,7 @@ Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89: 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. + Updated Imakefile function 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. @@ -3380,7 +3422,7 @@ Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89: 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. + Added DirectColor capability to xwdtopnm and pnmtoxwd. Speedup to pgmtolj from Arthur David Olson: avoid sending whitespace. Fix to pbmtogo from Bo Thide': 2D compression now works. @@ -3464,7 +3506,7 @@ Changes since the comp.sources.misc distribution of 31oct88: Changes since the X.V11R3 distribution of 31aug88: - The cbm format has been revised to support run-length encoding. + The cbm format has been revised to include run-length encoding. Pbmtops now does run-length encoding. Major changes since the X.V11R2 distribution of 28mar88: diff --git a/editor/pambackground.c b/editor/pambackground.c index 92e1fce2..8911adf0 100644 --- a/editor/pambackground.c +++ b/editor/pambackground.c @@ -111,17 +111,14 @@ selectBackground(struct pam * const pamP, tuple bg; /* Reference to one of ul, ur, ll, lr */ - if (pnm_tupleequal(pamP, ul, ur)) { - if (pnm_tupleequal(pamP, ll, ul)) - bg = ul; - else if (pnm_tupleequal(pamP, lr, ul)) - bg = ul; - } else if (pnm_tupleequal(pamP, ll, lr)) { - if (pnm_tupleequal(pamP, ul, ll)) - bg = ll; - else if (pnm_tupleequal(pamP, ur, ll)) - bg = ll; - } else { + if (pnm_tupleequal(pamP, ul, ur) && + (pnm_tupleequal(pamP, ul, ll) || + pnm_tupleequal(pamP, ul, lr))) + bg = ul; + else if (pnm_tupleequal(pamP, ll, lr) && + pnm_tupleequal(pamP, lr, ul)) + bg = ll; + else { /* No 3 corners are same color; look for 2 corners */ if (pnm_tupleequal(pamP, ul, ur)) /* top edge */ bg = ul; @@ -144,9 +141,9 @@ selectBackground(struct pam * const pamP, static void -computeBackground(struct pam * const pamP, - bool const verbose, - tuple * const bgColorP) { +determineBackgroundColor(struct pam * const pamP, + bool const verbose, + tuple * const bgColorP) { /*---------------------------------------------------------------------------- Determine what color is the background color of the image in the file represented by *pamP. @@ -195,49 +192,276 @@ computeBackground(struct pam * const pamP, } +static unsigned char const PT_UNKNOWN = 0; +static unsigned char const PT_BG = 1; +static unsigned char const PT_FG = 2; + + +static unsigned char ** +newPi(unsigned int const width, + unsigned int const height) { + + unsigned char ** pi; + unsigned int row; + + MALLOCARRAY(pi, height); + if (pi == NULL) + pm_error("Out of memory allocating %u row pointers", height); + for (row = 0; row < height; ++row) { + MALLOCARRAY(pi[row], width); + if (pi[row] == NULL) + pm_error("Out of memory allocating row %u", row); + } + return pi; +} + + static void -computeOutputRow(struct pam * const inpamP, - tuple * const inputTuplerow, - tuple const backgroundColor, - struct pam * const outpamP, - tuple * const outputTuplerow, - tuple const foreground, - tuple const background) { +destroyPi(const unsigned char *const * const pi, + unsigned int const height) { + unsigned int row; + + for (row = 0; row < height; ++row) + free((void*)pi[row]); + + free((void*)pi); +} + + + +static void +initPi(unsigned char ** const pi, + const struct pam * const pamP, + tuple const backgroundColor) { +/*---------------------------------------------------------------------------- + Set the initial info about every pixel in pi[][]. + + Read through the image in the file described by *inpamP and set each + pixel which is not of background color, and therefore obviously + foreground, to type PT_FG. Set every other pixel to PT_UNKNOWN. +-----------------------------------------------------------------------------*/ + 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) { + pi[row][col] = + pnm_tupleequal(pamP, tuplerow[col], backgroundColor) ? + PT_UNKNOWN : PT_FG; + } + } + pnm_freepamrow(tuplerow); +} + + + +static void +setEdges(unsigned char ** const pi, + unsigned int const width, + unsigned int const height) { +/*---------------------------------------------------------------------------- + Do the four edges. + + Anything of background color in an edge is background. Pixel type + PT_UNKNOWN implies background color, so we change all PT_UNKNOWN pixels + to PT_BG. +-----------------------------------------------------------------------------*/ + unsigned int row, col; + + for (col = 0; col < width; ++col) { + if (pi[0][col] == PT_UNKNOWN) + pi[0][col] = PT_BG; + + if (pi[height-1][col] == PT_UNKNOWN) + pi[height-1][col] = PT_BG; + } + for (row = 0; row < height; ++row) { + if (pi[row][0] == PT_UNKNOWN) + pi[row][0] = PT_BG; + + if (pi[row][width-1] == PT_UNKNOWN) + pi[row][width-1] = PT_BG; + } +} + + + +static void +expandBackgroundHoriz(unsigned char ** const pi, + unsigned int const width, + unsigned int const height, + bool * const expandedP) { +/*---------------------------------------------------------------------------- + In every row, expand the background rightward from any known background + pixel through all consecutive unknown pixels. + + Then do the same thing leftward. + + Iff we managed to expand the background at all, return *expandedP == TRUE. +-----------------------------------------------------------------------------*/ + unsigned int row; + bool expanded; + + for (row = 1, expanded = FALSE; row < height-1; ++row) { + int col; + + for (col = 1; col < width - 1; ++col) { + if (pi[row][col] == PT_UNKNOWN && pi[row][col-1] == PT_BG) { + expanded = TRUE; + /* Set the whole consecutive row of unknown to background */ + for (; pi[row][col] == PT_UNKNOWN && col < width - 1; ++col) + pi[row][col] = PT_BG; + } + } + + for (col = width-2; col > 0; --col) { + if (pi[row][col] == PT_UNKNOWN && pi[row][col+1] == PT_BG) { + expanded = TRUE; + /* Set the whole consecutive row of unknown to background */ + for (; pi[row][col] == PT_UNKNOWN && col > 0; --col) + pi[row][col] = PT_BG; + } + } + } + *expandedP = expanded; +} + + + +static void +expandBackgroundVert(unsigned char ** const pi, + unsigned int const width, + unsigned int const height, + bool * const expandedP) { +/*---------------------------------------------------------------------------- + In every column, expand the background downward from any known background + pixel through all consecutive unknown pixels. + + Then do the same thing upward. + + Iff we managed to expand the background at all, return *expandedP == TRUE. +-----------------------------------------------------------------------------*/ unsigned int col; - unsigned int firstForegroundCol; - // Column number of first column, going from the left, that is - // not the background color. - unsigned int endForegroundCol; - // Column number of the last column, going from the right, - // that is the background color. - - col = 0; - while (col < inpamP->width && - pnm_tupleequal(inpamP, inputTuplerow[col], backgroundColor)) - ++col; - - firstForegroundCol = col; - - col = inpamP->width; - while (col > firstForegroundCol && - pnm_tupleequal(inpamP, inputTuplerow[col-1], backgroundColor)) - --col; - - endForegroundCol = col; - - // If the row is all background, 'firstForegroundCol' and - // 'endForegroundCol' are both one past the right edge. - - for (col = 0; col < firstForegroundCol; ++col) - outputTuplerow[col] = background; - - for (col = firstForegroundCol; col < endForegroundCol; ++col) - outputTuplerow[col] = foreground; - - for (col = endForegroundCol; col < outpamP->width; ++col) - outputTuplerow[col] = background; + bool expanded; + + for (col = 1, expanded = FALSE; col < height-1; ++col) { + int row; + + for (row = 1; row < height - 1; ++row) { + if (pi[row][col] == PT_UNKNOWN && pi[row-1][col] == PT_BG) { + expanded = TRUE; + /* Set the whole consecutive col of unknown to background */ + for (; pi[row][col] == PT_UNKNOWN && row < height - 1; ++row) + pi[row][col] = PT_BG; + } + } + + for (row = height-2; row > 0; --row) { + if (pi[row][col] == PT_UNKNOWN && pi[row+1][col] == PT_BG) { + expanded = TRUE; + /* Set the whole consecutive col of unknown to background */ + for (; pi[row][col] == PT_UNKNOWN && row > 0; --row) + pi[row][col] = PT_BG; + } + } + } + *expandedP = expanded; +} + + + +static void +findBackgroundPixels(struct pam * const inpamP, + tuple const backgroundColor, + bool const verbose, + const unsigned char * const ** const piP) { +/*---------------------------------------------------------------------------- + Figure out which pixels of the image are background. Read the + image from the input file described by *inpamP (positioned now to + the start of the raster) and generate the matrix *piP telling which + pixels are background and which are foreground, given that the + background color is 'backgroundColor'. + + Note that it isn't as simple as finding which pixels are of color + 'backgroundColor'. They have to be part of a contiguous region that + touches one of the 4 edges of the image. + + In the matrix we return, there is one element for each pixel in the + image, and it has value PT_BG or PT_FG. +-----------------------------------------------------------------------------*/ + unsigned char ** pi; + bool backgroundComplete; + unsigned int passes; + + pi = newPi(inpamP->width, inpamP->height); + + initPi(pi, inpamP, backgroundColor); + + setEdges(pi, inpamP->width, inpamP->height); + + backgroundComplete = FALSE; + passes = 0; + + while (!backgroundComplete) { + bool expandedHoriz, expandedVert; + + expandBackgroundHoriz(pi, inpamP->width, inpamP->height, + &expandedHoriz); + + expandBackgroundVert(pi, inpamP->width, inpamP->height, + &expandedVert); + + backgroundComplete = !expandedHoriz && !expandedVert; + + ++passes; + } + + if (verbose) + pm_message("Background found in %u passes", passes); + + *piP = (const unsigned char * const *)pi; +} + + + +static void +writeOutput(const struct pam * const inpamP, + const unsigned char * const * const pi) { + + tuple black, white; + tuple * outputTuplerow; + /* This is not a normal tuplerow; it is just pointers to either + 'black' or 'white' + */ + unsigned int row; + struct pam outpam; + + initOutpam(inpamP, &outpam); + + allocateOutputPointerRow(outpam.width, &outputTuplerow); + pnm_createBlackTuple(&outpam, &black); + createWhiteTuple(&outpam, &white); + + pnm_writepaminit(&outpam); + + for (row = 0; row < outpam.height; ++row) { + unsigned int col; + for (col = 0; col < outpam.width; ++col) + outputTuplerow[col] = pi[row][col] == PT_BG ? white : black; + + pnm_writepamrow(&outpam, outputTuplerow); + } + pnm_freepamtuple(white); + pnm_freepamtuple(black); + free(outputTuplerow); } @@ -247,15 +471,10 @@ main(int argc, char *argv[]) { struct cmdlineInfo cmdline; struct pam inpam; - struct pam outpam; FILE * ifP; - tuple * inputTuplerow; - tuple * outputTuplerow; - // Not a regular tuple row -- just pointer array - unsigned int row; pm_filepos rasterpos; - tuple black, white; tuple backgroundColor; + const unsigned char * const * pi; pnm_init(&argc, argv); @@ -267,36 +486,19 @@ main(int argc, char *argv[]) { pm_tell2(ifP, &rasterpos, sizeof(rasterpos)); - computeBackground(&inpam, cmdline.verbose, &backgroundColor); - - initOutpam(&inpam, &outpam); - - inputTuplerow = pnm_allocpamrow(&inpam); - - allocateOutputPointerRow(outpam.width, &outputTuplerow); - pnm_createBlackTuple(&outpam, &black); - createWhiteTuple(&outpam, &white); - - pnm_writepaminit(&outpam); + determineBackgroundColor(&inpam, cmdline.verbose, &backgroundColor); pm_seek2(ifP, &rasterpos, sizeof(rasterpos)); - for (row = 0; row < outpam.height; ++row) { - pnm_readpamrow(&inpam, inputTuplerow); + findBackgroundPixels(&inpam, backgroundColor, cmdline.verbose, &pi); - computeOutputRow(&inpam, inputTuplerow, backgroundColor, - &outpam, outputTuplerow, black, white); + writeOutput(&inpam, pi); - pnm_writepamrow(&outpam, outputTuplerow); - } + destroyPi(pi, inpam.height); pm_close(ifP); - pnm_freepamrow(inputTuplerow); - free(outputTuplerow); pnm_freepamtuple(backgroundColor); - pnm_freepamtuple(white); - pnm_freepamtuple(black); return 0; } diff --git a/editor/pamcut.c b/editor/pamcut.c index d5de45fb..c6fc0cc1 100644 --- a/editor/pamcut.c +++ b/editor/pamcut.c @@ -94,8 +94,10 @@ parseCommandLine(int argc, char ** const argv, 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."); + pm_error("Wrong number of arguments: %u. The only argument in " + "the preferred syntax is an optional input file name. " + "In older syntax, there are also forms with 4 and 5 " + "arguments.", argc-1); switch (argc-1) { case 0: diff --git a/editor/pamditherbw.c b/editor/pamditherbw.c index 931b475f..49e78a4e 100644 --- a/editor/pamditherbw.c +++ b/editor/pamditherbw.c @@ -19,7 +19,12 @@ #include "shhopt.h" #include "pm_gamma.h" -enum halftone {QT_FS, QT_THRESH, QT_DITHER8, QT_CLUSTER, QT_HILBERT}; +enum halftone {QT_FS, + QT_ATKINSON, + QT_THRESH, + QT_DITHER8, + QT_CLUSTER, + QT_HILBERT}; enum ditherType {DT_REGULAR, DT_CLUSTER}; @@ -53,7 +58,7 @@ parseCommandLine(int argc, char ** argv, optStruct3 opt; unsigned int option_def_index; - unsigned int floydOpt, hilbertOpt, thresholdOpt, dither8Opt, + unsigned int floydOpt, atkinsonOpt, hilbertOpt, thresholdOpt, dither8Opt, cluster3Opt, cluster4Opt, cluster8Opt; unsigned int valueSpec, clumpSpec; @@ -62,6 +67,7 @@ parseCommandLine(int argc, char ** argv, 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, "atkinson", OPT_FLAG, NULL, &atkinsonOpt, 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); @@ -84,15 +90,17 @@ parseCommandLine(int argc, char ** argv, optParseOptions3(&argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ - if (floydOpt + thresholdOpt + hilbertOpt + dither8Opt + + if (floydOpt + atkinsonOpt + thresholdOpt + hilbertOpt + dither8Opt + cluster3Opt + cluster4Opt + cluster8Opt == 0) cmdlineP->halftone = QT_FS; - else if (floydOpt + thresholdOpt + dither8Opt + + else if (floydOpt + atkinsonOpt + thresholdOpt + dither8Opt + cluster3Opt + cluster4Opt + cluster8Opt > 1) - pm_error("No cannot specify more than one halftoning type"); + pm_error("Cannot specify more than one halftoning type"); else { if (floydOpt) cmdlineP->halftone = QT_FS; + else if (atkinsonOpt) + cmdlineP->halftone = QT_ATKINSON; else if (thresholdOpt) cmdlineP->halftone = QT_THRESH; else if (hilbertOpt) { @@ -389,8 +397,15 @@ struct converter { struct fsState { float * thiserr; + /* thiserr[N] is the power from previous pixels to include in + future column N of the current row. + */ float * nexterr; + /* nexterr[N] is the power from previous pixels to include in + future column N of the next row. + */ bool fs_forward; + /* We're going forward (left to right) through the current row */ samplen threshval; /* The power value we consider to be half white */ }; @@ -426,32 +441,32 @@ fsConvertRow(struct converter * const converterP, sum = pm_ungamma709(grayrow[col][0]) + thiserr[col + 1]; if (sum >= stateP->threshval) { - /* We've accumulated enough light to justify a white output - pixel. + /* We've accumulated enough light (power) to justify a + white output pixel. */ bitrow[col][0] = PAM_BW_WHITE; - /* Remove from sum the power of the white output pixel */ + /* Remove from sum the power of this 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. + /* Forward to future output pixels the power from current + input pixel and the power forwarded from previous input + pixels to the current pixel, less 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; + nexterr[col + 2] += (sum * 1) / 16; ++col; } else { thiserr[col ] += (sum * 7) / 16; nexterr[col + 2] += (sum * 3) / 16; nexterr[col + 1] += (sum * 5) / 16; - nexterr[col ] += (sum ) / 16; + nexterr[col ] += (sum * 1) / 16; --col; } @@ -466,7 +481,13 @@ fsConvertRow(struct converter * const converterP, static void fsDestroy(struct converter * const converterP) { - free(converterP->stateP); + + struct fsState * const stateP = converterP->stateP; + + free(stateP->thiserr); + free(stateP->nexterr); + + free(stateP); } @@ -507,6 +528,144 @@ createFsConverter(struct pam * const graypamP, +struct atkinsonState { + float * error[3]; + /* error[R][C] is the power from previous pixels to include + in column C of the Rth row down from the current row + (0th row is the current row). + + No error propagates down more than two rows. + + For R == 0, C is a column we haven't done yet. + */ + samplen threshval; + /* The power value we consider to be half white */ +}; + + +static void +moveAtkinsonErrorWindowDown(struct converter * const converterP) { + + struct atkinsonState * const stateP = converterP->stateP; + + float * const oldError0 = stateP->error[0]; + + unsigned int relRow; + unsigned int col; + + for (relRow = 0; relRow < 2; ++relRow) + stateP->error[relRow] = stateP->error[relRow+1]; + + for (col = 0; col < converterP->cols + 2; ++col) + oldError0[col] = 0.0; + + stateP->error[2] = oldError0; +} + + + +static void +atkinsonConvertRow(struct converter * const converterP, + unsigned int const row, + tuplen grayrow[], + tuple bitrow[]) { + + /* See http://www.tinrocket.com/projects/programming/graphics/00158/ + for a description of the Atkinson algorithm + */ + + struct atkinsonState * const stateP = converterP->stateP; + + samplen ** const error = stateP->error; + + unsigned int col; + + for (col = 0; col < converterP->cols; ++col) { + samplen sum; + + sum = pm_ungamma709(grayrow[col][0]) + error[0][col + 1]; + if (sum >= stateP->threshval) { + /* We've accumulated enough light (power) to justify a + white output pixel. + */ + bitrow[col][0] = PAM_BW_WHITE; + /* Remove from sum the power of this white output pixel */ + sum -= 2*stateP->threshval; + } else + bitrow[col][0] = PAM_BLACK; + + /* Forward to future output pixels 3/4 of the power from current + input pixel and the power forwarded from previous input + pixels to the current pixel, less any power we put into the + current output pixel. + */ + error[0][col+1] += sum/8; + error[0][col+2] += sum/8; + if (col > 0) + error[1][col-1] += sum/8; + error[1][col ] += sum/8; + error[1][col+1] += sum/8; + error[2][col ] += sum/8; + } + + moveAtkinsonErrorWindowDown(converterP); +} + + + +static void +atkinsonDestroy(struct converter * const converterP) { + + struct atkinsonState * const stateP = converterP->stateP; + + unsigned int relRow; + + for (relRow = 0; relRow < 3; ++relRow) + free(stateP->error[relRow]); + + free(stateP); +} + + + +static struct converter +createAtkinsonConverter(struct pam * const graypamP, + float const threshFraction) { + + struct atkinsonState * stateP; + struct converter converter; + unsigned int relRow; + + converter.cols = graypamP->width; + converter.convertRow = &atkinsonConvertRow; + converter.destroy = &atkinsonDestroy; + + MALLOCVAR_NOFAIL(stateP); + + for (relRow = 0; relRow < 3; ++relRow) + MALLOCARRAY_NOFAIL(stateP->error[relRow], graypamP->width + 2); + + srand(pm_randseed()); + + { + /* (random errors in [-1/8 .. 1/8]) */ + unsigned int col; + for (col = 0; col < graypamP->width + 2; ++col) { + stateP->error[0][col] = ((float)rand()/RAND_MAX - 0.5) / 4; + stateP->error[1][col] = 0.0; + stateP->error[2][col] = 0.0; + } + } + + stateP->threshval = threshFraction; + + converter.stateP = stateP; + + return converter; +} + + + struct threshState { samplen threshval; }; @@ -703,6 +862,9 @@ main(int argc, char *argv[]) { case QT_FS: converter = createFsConverter(&graypam, cmdline.threshval); break; + case QT_ATKINSON: + converter = createAtkinsonConverter(&graypam, cmdline.threshval); + break; case QT_THRESH: converter = createThreshConverter(&graypam, cmdline.threshval); break; diff --git a/editor/pamflip.c b/editor/pamflip.c index 59b60b56..0c2aabb4 100644 --- a/editor/pamflip.c +++ b/editor/pamflip.c @@ -171,7 +171,7 @@ parseCommandLine(int argc, char ** const argv, 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)); + optEntry * option_def; /* Instructions to OptParseOptions3 on how to parse our options. */ optStruct3 opt; @@ -183,6 +183,8 @@ parseCommandLine(int argc, char ** const argv, unsigned int memsizeOpt; const char *xformOpt; + MALLOCARRAY(option_def, 100); + option_def_index = 0; /* incremented by OPTENTRY */ OPTENT3(0, "lr", OPT_FLAG, NULL, &lr, 0); OPTENT3(0, "leftright", OPT_FLAG, NULL, &lr, 0); @@ -860,7 +862,7 @@ main(int argc, char * argv[]) { struct cmdlineInfo cmdline; struct pam inpam; struct pam outpam; - FILE* ifP; + FILE * ifP; struct xformMatrix xform; pnm_init(&argc, argv); diff --git a/editor/pammixinterlace.c b/editor/pammixinterlace.c index 1421c7a2..579a8092 100644 --- a/editor/pammixinterlace.c +++ b/editor/pammixinterlace.c @@ -3,7 +3,7 @@ ******************************************************************************* De-interlace an image by merging adjacent rows. - Copyright (C) 2005 Bruce Guenter, FutureQuest, Inc. + Copyright (C) 2007 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, @@ -14,32 +14,184 @@ ******************************************************************************/ -#include "pam.h" -#include "shhopt.h" +#include <string.h> + #include "mallocvar.h" +#include "nstring.h" +#include "shhopt.h" +#include "pam.h" + + + +static sample +clamp(sample const val, + sample const maxval) { + + return val < 0 ? 0 : val > maxval ? maxval : val; +} + + + +static bool +distant(long const above, + long const mid, + long const below) { + + return abs(mid - (above + below) / 2) > abs(above - below); +} + + + +static void +filterLinearBlend(tuple * const outputrow, + tuple ** const tuplerowWindow, + unsigned int const width, + unsigned int const depth, + bool const adaptive, + sample const maxval) { + + unsigned int col; + + for (col = 0; col < width; ++col) { + unsigned int plane; + + for (plane = 0; plane < depth; ++plane) { + long const above = tuplerowWindow[0][col][plane]; + long const mid = tuplerowWindow[1][col][plane]; + long const below = tuplerowWindow[2][col][plane]; + + sample out; + + if (!adaptive || distant(above, mid, below)) + out = (above + mid * 2 + below) / 4; + else + out = mid; + + outputrow[col][plane] = out; + } + } +} + + + +static void +filterFfmpeg(tuple * const outputrow, + tuple ** const tuplerowWindow, + unsigned int const width, + unsigned int const depth, + bool const adaptive, + sample const maxval) { + + unsigned int col; + + for (col = 0; col < width; ++col) { + unsigned int plane; + + for (plane = 0; plane < depth; ++plane) { + long const above = tuplerowWindow[1][col][plane]; + long const mid = tuplerowWindow[2][col][plane]; + long const below = tuplerowWindow[3][col][plane]; + + sample out; + + if (!adaptive || distant(above, mid, below)) { + long const a = (- (long)tuplerowWindow[0][col][plane] + + above * 4 + + mid * 2 + + below * 4 + - (long)tuplerowWindow[4][col][plane]) / 8; + out = clamp(a, maxval); + } else + out = mid; + + outputrow[col][plane] = out; + } + } +} + + + +static void +filterFIR(tuple * const outputrow, + tuple ** const tuplerowWindow, + unsigned int const width, + unsigned int const depth, + bool const adaptive, + sample const maxval) { + + unsigned int col; + + for (col = 0; col < width; ++col) { + unsigned int plane; + + for (plane = 0; plane < depth; ++plane) { + + long const above = tuplerowWindow[1][col][plane]; + long const mid = tuplerowWindow[2][col][plane]; + long const below = tuplerowWindow[3][col][plane]; + + sample out; + + if (!adaptive || distant(above, mid, below)) { + long const a = (- (long)tuplerowWindow[0][col][plane] + + above * 2 + + mid * 6 + + below * 2 + - (long)tuplerowWindow[4][col][plane]) / 8; + out = clamp(a, maxval); + } else + out = mid; + + outputrow[col][plane] = out; + } + } +} + + + +struct filter { + const char * name; /* The command-line name of the filter */ + unsigned int rows; /* The number of rows the filter operates on */ + void (*filter)(tuple *, tuple **, + unsigned int width, unsigned int depth, + bool adaptive, sample maxval); +}; + +static struct filter filters[] = { + { "fir", 5, filterFIR }, /* FIR is cleanest and default filter */ + { "ffmpeg", 5, filterFfmpeg }, + { "linear", 3, filterLinearBlend }, +}; 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 */ + const char * inputFileName; /* Names of input files */ + struct filter * filterP; + unsigned int adaptive; }; + static void parseCommandLine(int argc, char ** argv, - struct cmdlineInfo *cmdlineP) { + 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. -----------------------------------------------------------------------------*/ optStruct3 opt; /* set by OPTENT3 */ - optEntry *option_def; + optEntry * option_def; unsigned int option_def_index; + const char * filterName; + unsigned int filterSpec; MALLOCARRAY_NOFAIL(option_def, 100); option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "filter", OPT_STRING, &filterName, &filterSpec, 0); + OPTENT3(0, "adaptive", OPT_FLAG, NULL, &cmdlineP->adaptive, 0); opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ @@ -48,10 +200,25 @@ parseCommandLine(int argc, char ** argv, optParseOptions3(&argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + if (!filterSpec) + cmdlineP->filterP = &filters[0]; + else { + unsigned int i; + cmdlineP->filterP = NULL; + for (i = 0; i < sizeof filters / sizeof(struct filter); ++i) { + if (STRCASEEQ(filterName, filters[i].name)) { + cmdlineP->filterP = &filters[i]; + break; + } + } + if (!cmdlineP->filterP) + pm_error("The filter name '%s' is not known.", filterName); + } + if (argc-1 < 1) - cmdlineP->inputFilespec = "-"; + cmdlineP->inputFileName = "-"; else if (argc-1 == 1) - cmdlineP->inputFilespec = argv[1]; + cmdlineP->inputFileName = argv[1]; else pm_error("You specified too many arguments (%d). The only " "argument is the optional input file specification.", @@ -62,40 +229,44 @@ parseCommandLine(int argc, char ** argv, static void allocateRowWindowBuffer(struct pam * const pamP, - tuple ** const tuplerow) { + tuple ** const tuplerowWindow, + unsigned int const rows) { unsigned int row; - for (row = 0; row < 3; ++row) - tuplerow[row] = pnm_allocpamrow(pamP); + for (row = 0; row < rows; ++row) + tuplerowWindow[row] = pnm_allocpamrow(pamP); } static void -freeRowWindowBuffer(tuple ** const tuplerow) { +freeRowWindowBuffer(tuple ** const tuplerowWindow, + unsigned int const rows) { unsigned int row; - for (row = 0; row < 3; ++row) - pnm_freepamrow(tuplerow[row]); - + for (row = 0; row < rows; ++row) + pnm_freepamrow(tuplerowWindow[row]); } static void -slideWindowDown(tuple ** const tuplerow) { +slideWindowDown(tuple ** const tuplerowWindow, + unsigned int const rows) { /*---------------------------------------------------------------------------- - Slide the 3-line tuple row window tuplerow[] down one row by moving + Slide the rows-line tuple row window tuplerowWindow[] down one 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; + tuple * const oldrow0 = tuplerowWindow[0]; + + unsigned int i; + + for (i = 0; i < rows-1; ++i) + tuplerowWindow[i] = tuplerowWindow[i+1]; + + tuplerowWindow[i] = oldrow0; } @@ -107,14 +278,17 @@ main(int argc, char *argv[]) { struct cmdlineInfo cmdline; struct pam inpam; struct pam outpam; - tuple * tuplerow[3]; + tuple * tuplerowWindow[5]; tuple * outputrow; + unsigned int rows; - pnm_init( &argc, argv ); + pnm_init(&argc, argv); parseCommandLine(argc, argv, &cmdline); - ifP = pm_openr(cmdline.inputFilespec); + rows = cmdline.filterP->rows; + + ifP = pm_openr(cmdline.inputFileName); pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type)); @@ -123,48 +297,43 @@ main(int argc, char *argv[]) { pnm_writepaminit(&outpam); - allocateRowWindowBuffer(&inpam, tuplerow); + allocateRowWindowBuffer(&inpam, tuplerowWindow, rows); outputrow = pnm_allocpamrow(&outpam); - if (inpam.height < 3) { + if (inpam.height < rows) { unsigned int row; - pm_message("WARNING: Image height less than 3. No mixing done."); + pm_message("WARNING: Image height less than %d. No mixing done.", + rows); for (row = 0; row < inpam.height; ++row) { - pnm_readpamrow(&inpam, tuplerow[0]); - pnm_writepamrow(&outpam, tuplerow[0]); + pnm_readpamrow(&inpam, tuplerowWindow[0]); + pnm_writepamrow(&outpam, tuplerowWindow[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]); + for (row = 0; row < rows-1; ++row) + pnm_readpamrow(&inpam, tuplerowWindow[row]); + + /* Pass through first unfilterable rows */ + for (row = 0; row < rows/2; ++row) + pnm_writepamrow(&outpam, tuplerowWindow[row]); + + for (row = rows / 2 + 1; row < inpam.height - rows / 2 + 1; ++row) { + pnm_readpamrow(&inpam, tuplerowWindow[rows-1]); + cmdline.filterP->filter(outputrow, tuplerowWindow, + inpam.width, inpam.depth, + cmdline.adaptive, inpam.maxval); + pnm_writepamrow(&outpam, outputrow); + + slideWindowDown(tuplerowWindow, rows); + } + + /* Pass through last rows */ + for (row = rows/2; row < rows-1; ++row) + pnm_writepamrow(&outpam, tuplerowWindow[row]); } - freeRowWindowBuffer(tuplerow); + freeRowWindowBuffer(tuplerowWindow, rows); pnm_freepamrow(outputrow); pm_close(inpam.file); pm_close(outpam.file); diff --git a/editor/ppm3d.c b/editor/ppm3d.c index c37ceeb1..6f317a0b 100644 --- a/editor/ppm3d.c +++ b/editor/ppm3d.c @@ -1,18 +1,99 @@ -/* 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. -*/ +/*============================================================================= + ppmto3d +=============================================================================== + This program converts two PPM images into an anaglyph stereogram image PPM. + (for viewing with red/blue 3D glasses). +=============================================================================*/ + +#include <assert.h> + +#include "shhopt.h" +#include "mallocvar.h" #include "ppm.h" #include "lum.h" + + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char * leftInputFileName; /* '-' if stdin */ + const char * rghtInputFileName; /* '-' if stdin */ + int offset; + unsigned int color; +}; + + + +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 offsetSpec; + const char * offsetArg; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "color", OPT_FLAG, NULL, + &cmdlineP->color, 0); + OPTENT3(0, "offset", OPT_INT, &cmdlineP->offset, + &offsetSpec, 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 < 2) + pm_error("You must specify at least two arguments: left and right " + "input file names. You specified %u", argc-1); + else { + cmdlineP->leftInputFileName = argv[1]; + cmdlineP->rghtInputFileName = argv[2]; + + if (argc-1 > 2) { + offsetArg = argv[3]; + + if (argc-1 > 3) + pm_error("Program takes at most 3 arguments: left and " + "right input file names and offset. " + "You specified %u", argc-1); + } else + offsetArg = NULL; + } + + if (offsetArg && offsetSpec) + pm_error("You cannot specify both -offset and the offset " + "argument (i.e. with -offset, there is at most " + "two arguments: left and right input file names"); + else if (!offsetArg && !offsetSpec) + cmdlineP->offset = 0; + else if (offsetArg) + cmdlineP->offset = atoi(offsetArg); +} + + + static void computeGrayscaleRow(const pixel * const inputRow, gray * const outputRow, @@ -34,105 +115,193 @@ computeGrayscaleRow(const pixel * const inputRow, +static void +compute3dRowMono(gray * const lGrayrow, + gray * const rGrayrow, + pixel * const pixelrow, + unsigned int const cols, + int const offset) { + + unsigned int col; + gray * lgP; + gray * rgP; + pixel * pP; + + assert(abs(offset) <= cols); + + for (col = 0, pP = pixelrow, lgP = lGrayrow, rgP = rGrayrow; + col < cols + offset; + ++col) { + + if ((int)col < offset/2) + ++lgP; + else if ((int)col < offset) { + PPM_ASSIGN(*pP, 0, *lgP, *lgP); + ++lgP; + ++pP; + } else if (col < cols) { + PPM_ASSIGN(*pP, *rgP, *lgP, *lgP); + ++lgP; + ++rgP; + ++pP; + } else if (col < cols + offset/2) { + PPM_ASSIGN(*pP, *rgP, 0, 0); + ++rgP; + ++pP; + } else { + assert(col < cols + offset); + ++rgP; + } + } +} + + + +static void +compute3dRowColor(pixel * const lPixelrow, + pixel * const rPixelrow, + pixel * const pixelrow, + unsigned int const cols, + unsigned int const offset) { + + unsigned int col; + pixel * lP; + pixel * rP; + pixel * pP; + + assert(abs(offset) <= cols); + + for (col = 0, pP = pixelrow, lP = lPixelrow, rP = rPixelrow; + col < cols + offset; + ++col) { + + if ((int)col < offset/2) + ++lP; + else if ((int)col < offset) { + PPM_ASSIGN(*pP, 0, PPM_GETG(*lP), PPM_GETB(*lP)); + ++lP; + ++pP; + } else if (col < cols) { + PPM_ASSIGN(*pP, PPM_GETR(*rP), PPM_GETG(*lP), PPM_GETB(*lP)); + ++lP; + ++rP; + ++pP; + } else if (col < cols + offset/2) { + PPM_ASSIGN(*pP, PPM_GETR(*rP), 0, 0); + ++rP; + ++pP; + } else { + assert(col < cols + offset); + ++rP; + } + } +} + + + +static void +write3dRaster(FILE * const ofP, + FILE * const lIfP, + FILE * const rIfP, + unsigned int const cols, + unsigned int const rows, + pixval const maxval, + int const lFormat, + int const rFormat, + int const offset, + bool const color) { + + pixel * lPixelrow; + gray * lGrayrow; + pixel * rPixelrow; + gray * rGrayrow; + pixel * pixelrow; + + unsigned int row; + + assert(abs(offset) < cols); + + 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); + + if (color) + compute3dRowColor(lPixelrow, rPixelrow, pixelrow, cols, offset); + else + compute3dRowMono(lGrayrow, rGrayrow, pixelrow, cols, offset); + + ppm_writeppmrow(ofP, pixelrow, cols, maxval, 0); + } + + ppm_freerow(pixelrow); + pgm_freerow(rGrayrow); + ppm_freerow(rPixelrow); + pgm_freerow(lGrayrow); + ppm_freerow(lPixelrow); +} + + + int -main (int argc, char *argv[]) { +main(int argc, char *argv[]) { + + struct cmdlineInfo cmdline; + FILE * lIfP; + FILE * rIfP; - int offset; - int cols, rows, row; - pixel* pixelrow; + int cols, rows; pixval maxval; - FILE* Lifp; - pixel* Lpixelrow; - gray* Lgrayrow; - int Lrows, Lcols, Lformat; - pixval Lmaxval; + int lRows, lCols; + int lFormat; + pixval lMaxval; - FILE* Rifp; - pixel* Rpixelrow; - gray* Rgrayrow; - int Rrows, Rcols, Rformat; - pixval Rmaxval; + int rRows, rCols; + int rFormat; + pixval rMaxval; - ppm_init (&argc, argv); + 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); + parseCommandLine(argc, argv, &cmdline); - Lifp = pm_openr (argv[1]); - Rifp = pm_openr (argv[2]); + lIfP = pm_openr(cmdline.leftInputFileName); + rIfP = pm_openr(cmdline.rghtInputFileName); - 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); + 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))) + 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; + cols = lCols; + rows = lRows; + maxval = lMaxval; + + if (abs(cmdline.offset) >= cols) + pm_error("Magnitude of -offset (%u columns) is not less than " + "width of images " + "(%u columns)", abs(cmdline.offset), cols); - 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); + ppm_writeppminit(stdout, cols, rows, maxval, 0); - 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); - } + write3dRaster(stdout, lIfP, rIfP, cols, rows, maxval, + lFormat, rFormat, cmdline.offset, cmdline.color); - pm_close(Lifp); - pm_close(Rifp); + pm_close(lIfP); + pm_close(rIfP); pm_close(stdout); return 0; } + diff --git a/editor/ppmbrighten.c b/editor/ppmbrighten.c index 93649082..1dcdbd7f 100644 --- a/editor/ppmbrighten.c +++ b/editor/ppmbrighten.c @@ -21,7 +21,7 @@ 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 * inputFilespec; /* '-' if stdin */ float saturation; float value; unsigned int normalize; @@ -29,10 +29,9 @@ struct cmdlineInfo { - static void -parseCommandLine (int argc, char ** argv, - struct cmdlineInfo *cmdlineP) { +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. @@ -270,9 +269,9 @@ int main(int argc, char * argv[]) { struct cmdlineInfo cmdline; - FILE *ifP; + FILE * ifP; pixval minValue, maxValue; - pixel *pixelrow; + pixel * pixelrow; pixval maxval; int rows, cols, format, row; diff --git a/lib/fileio.h b/lib/fileio.h index 158da10a..586c8265 100644 --- a/lib/fileio.h +++ b/lib/fileio.h @@ -1,5 +1,7 @@ -#ifndef _NETPBM_FILEIO_H_ -#define _NETPBM_FILEIO_H_ +#ifndef FILEIO_H_INCLUDED +#define FILEIO_H_INCLUDED + +#include <stdio.h> char pm_getc(FILE * const file); diff --git a/lib/libpam.c b/lib/libpam.c index f725a916..362c1159 100644 --- a/lib/libpam.c +++ b/lib/libpam.c @@ -687,10 +687,10 @@ pnm_readpaminitrestaspnm(FILE * const fileP, -----------------------------------------------------------------------------*/ struct pam pam; - pam.size = sizeof(struct pam); - pam.file = fileP; - pam.len = PAM_STRUCT_SIZE(tuple_type); - pam.format = PAM_FORMAT; + pam.size = sizeof(struct pam); + pam.file = fileP; + pam.len = PAM_STRUCT_SIZE(tuple_type); + pam.format = PAM_FORMAT; readpaminitrest(&pam); @@ -792,7 +792,8 @@ pnm_readpaminit(FILE * const file, break; default: - pm_error("bad magic number - not a PAM, PPM, PGM, or PBM file"); + pm_error("bad magic number 0x%x - not a PAM, PPM, PGM, or PBM file", + pamP->format); } pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval); diff --git a/lib/libpamread.c b/lib/libpamread.c index 85701a90..c03d730c 100644 --- a/lib/libpamread.c +++ b/lib/libpamread.c @@ -16,9 +16,9 @@ #include <limits.h> #include <assert.h> -#include "pam.h" #include "fileio.h" #include "nstring.h" +#include "pam.h" static void diff --git a/lib/libpbm3.c b/lib/libpbm3.c index 34922edd..2a811748 100644 --- a/lib/libpbm3.c +++ b/lib/libpbm3.c @@ -133,6 +133,14 @@ packBitsWithMmxSse(FILE * const fileP, +static unsigned int +bitValue(unsigned char const byteValue) { + + return byteValue == 0 ? 0 : 1; +} + + + static void packBitsGeneric(FILE * const fileP, const bit * const bitrow, @@ -140,7 +148,7 @@ packBitsGeneric(FILE * const fileP, unsigned int const cols, unsigned int * const nextColP) { /*---------------------------------------------------------------------------- - Pack the bits of bitrow[] into byts at 'packedBits'. Going left to right, + 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. @@ -149,18 +157,16 @@ packBitsGeneric(FILE * const fileP, -----------------------------------------------------------------------------*/ unsigned int col; - #define iszero(x) ((x) == 0 ? 0 : 1) - for (col = 0; col + 7 < cols; 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 + bitValue(bitrow[col+0]) << 7 | + bitValue(bitrow[col+1]) << 6 | + bitValue(bitrow[col+2]) << 5 | + bitValue(bitrow[col+3]) << 4 | + bitValue(bitrow[col+4]) << 3 | + bitValue(bitrow[col+5]) << 2 | + bitValue(bitrow[col+6]) << 1 | + bitValue(bitrow[col+7]) << 0 ); *nextColP = col; } diff --git a/lib/libpm.c b/lib/libpm.c index f36b7a50..88b790e6 100644 --- a/lib/libpm.c +++ b/lib/libpm.c @@ -1662,3 +1662,32 @@ pm_check(FILE * const file, +void +pm_drain(FILE * const fileP, + uint const limit, + uint * const bytesReadP) { +/*---------------------------------------------------------------------------- + Read bytes from *fileP until EOF and return as *bytesReadP how many there + were. + + But don't read any more than 'limit'. + + This is a good thing to call after reading an input file to be sure you + didn't leave some input behind, which could mean you didn't properly + interpret the file. +-----------------------------------------------------------------------------*/ + uint bytesRead; + bool eof; + + for (bytesRead = 0, eof = false; !eof && bytesRead < 4096;) { + + int rc; + + rc = fgetc(fileP); + + eof = (rc == EOF); + if (!eof) + ++bytesRead; + } + *bytesReadP = bytesRead; +} diff --git a/lib/pm.h b/lib/pm.h index 8265c9ea..232fc363 100644 --- a/lib/pm.h +++ b/lib/pm.h @@ -345,6 +345,11 @@ pm_check(FILE * const file, pm_filepos const need_raster_size, enum pm_check_code * const retval_p); +void +pm_drain(FILE * const fileP, + unsigned int const limit, + unsigned int * const bytesReadP); + char * pm_arg0toprogname(const char arg0[]); diff --git a/lib/util/pm_c_util.h b/lib/util/pm_c_util.h index f21a2f82..db159284 100644 --- a/lib/util/pm_c_util.h +++ b/lib/util/pm_c_util.h @@ -15,6 +15,14 @@ #define ROUND(X) (((X) >= 0) ? (int)((X)+0.5) : (int)((X)-0.5)) #undef ROUNDU #define ROUNDU(X) ((unsigned int)((X)+0.5)) + +/* ROUNDUP rounds up to a specified multiple. E.g. ROUNDUP(22, 8) == 24 */ + +#undef ROUNDUP +#define ROUNDUP(X,M) (((X)+(M)-1)/(M)*(M)) +#undef ROUNDDN +#define ROUNDDN(X,M) ((X)/(M)*(M)) + #undef SQR #define SQR(a) ((a)*(a)) diff --git a/lib/util/shhopt.c b/lib/util/shhopt.c index 15058bb5..718186fa 100644 --- a/lib/util/shhopt.c +++ b/lib/util/shhopt.c @@ -89,9 +89,13 @@ optStructCount(const optEntry opt[]) return ret; } + + +static int +optMatch(optEntry const opt[], + const char * const s, + int const lng) { /*------------------------------------------------------------------------ - | NAME optMatch - | | FUNCTION Find a matching option. | | INPUT opt array of possible options. @@ -103,35 +107,39 @@ optStructCount(const optEntry opt[]) | 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); + unsigned int const nopt = optStructCount(opt); + + unsigned int q; + unsigned int matchlen; + const char * p; + + matchlen = 0; /* initial value */ + if (lng) { if ((p = strchr(s, '=')) != NULL) matchlen = p - s; else matchlen = strlen(s); } - for (q = 0; q < nopt; q++) { + for (q = 0; q < nopt; ++q) { if (lng) { - if (!opt[q].longName) - continue; - if (strncmp(s, opt[q].longName, matchlen) == 0) - return q; + if (opt[q].longName) { + if (strncmp(s, opt[q].longName, matchlen) == 0) + return q; + } } else { - if (!opt[q].shortName) - continue; - if (*s == opt[q].shortName) - return q; + if (opt[q].shortName) { + if (s[0] == opt[q].shortName) + return q; + } } } return -1; } + + /*------------------------------------------------------------------------ | NAME optString | @@ -422,7 +430,7 @@ optExecute(optEntry const opt, char *arg, int lng) case OPT_UINT: case OPT_ULONG: { unsigned long tmp; - char *e; + char * tailPtr; if (arg == NULL) optFatal("internal error: optExecute() called with NULL argument " @@ -431,9 +439,8 @@ optExecute(optEntry const opt, char *arg, int lng) if (arg[0] == '-' || arg[1] == '+') optFatal("unsigned number '%s' has a sign ('%c')", arg, arg[0]); - - tmp = strtoul(arg, &e, 10); - if (*e) + tmp = strtoul(arg, &tailPtr, 10); + if (*tailPtr) optFatal("invalid number `%s'", arg); if (errno == ERANGE || (opt.type == OPT_UINT && tmp > UINT_MAX)) @@ -648,7 +655,15 @@ static void parse_short_option_token(char *argv[], const int argc, const int ai, const optEntry opt_table[], int * const tokens_consumed_p) { +/*---------------------------------------------------------------------------- + Parse a cluster of short options, e.g. -walne . + + The last option in the cluster might take an argument, and we parse + that as well. e.g. -cf myfile or -cfmyfile . + argv[] and argc describe the whole program argument set. 'ai' is the + index of the argument that is the short option cluster. +-----------------------------------------------------------------------------*/ char *o; /* A short option character */ char *arg; int mi; /* index into option table */ @@ -690,11 +705,60 @@ parse_short_option_token(char *argv[], const int argc, const int ai, 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) { +fatalUnrecognizedLongOption(const char * const optionName, + optEntry const optTable[]) { + + unsigned int const nopt = optStructCount(optTable); + + unsigned int q; + + char optList[1024]; + + optList[0] = '\0'; /* initial value */ + + for (q = 0; + q < nopt && strlen(optList) + 1 <= sizeof(optList); + ++q) { + + const optEntry * const optEntryP = &optTable[q]; + const char * entry; + + if (optEntryP->longName) + asprintfN(&entry, "-%s ", optEntryP->longName); + else + asprintfN(&entry, "-%c ", optEntryP->shortName); + + strncat(optList, entry, sizeof(optList) - strlen(optList) - 1); + + strfree(entry); + + if (strlen(optList) + 1 == sizeof(optList)) { + /* Buffer is full. Overwrite end of list with ellipsis */ + strcpy(&optList[sizeof(optList) - 4], "..."); + } + } + optFatal("unrecognized option '%s'. Recognized options are: %s", + optionName, optList); +} + + + +static void +parse_long_option(char * const argv[], + int const argc, + int const ai, + int const namepos, + optEntry const opt_table[], + int * const tokens_consumed_p) { +/*---------------------------------------------------------------------------- + Parse a long option, e.g. -verbose or --verbose. + + The option might take an argument, and we parse + that as well. e.g. -file=myfile or -file myfile . + argv[] and argc describe the whole program argument set. 'ai' is the + index of the argument that is the long option. +-----------------------------------------------------------------------------*/ char *equals_arg; /* The argument of an option, included in the same token, after a "=". NULL if no "=" in the token. @@ -708,8 +772,8 @@ parse_long_option(char *argv[], const int argc, const int ai, *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]); - + fatalUnrecognizedLongOption(argv[ai], opt_table); + /* possibly locate the argument to this option. */ { char *p; @@ -732,7 +796,8 @@ parse_long_option(char *argv[], const int argc, const int ai, } } else { if (equals_arg) - optFatal("option `%s' doesn't allow an argument", + optFatal("option `%s' doesn't allow an argument, but you " + "have specified it in the form name=value", optString(opt_table[mi], 1)); else arg = NULL; diff --git a/other/Makefile b/other/Makefile index 139527e3..a38766fc 100644 --- a/other/Makefile +++ b/other/Makefile @@ -24,7 +24,7 @@ endif # build. PORTBINARIES = pamarith pambayer pamchannel pamdepth \ - pamendian pamlookup pampick pamsplit \ + pamendian pamfixtrunc pamlookup pampick pamsplit \ pamstack pamsummcol pnmcolormap \ ppmdcfont ppmddumpfont ppmdmkfont diff --git a/other/pamfixtrunc.c b/other/pamfixtrunc.c new file mode 100644 index 00000000..7bf2435e --- /dev/null +++ b/other/pamfixtrunc.c @@ -0,0 +1,175 @@ +/*============================================================================ + pamfixtrunc +============================================================================== + Fix a Netpbm image that has been truncated, e.g. by I/O error. + + By Bryan Henderson, January 2007. + + Contributed to the public domain by its author. + +============================================================================*/ + +#include <setjmp.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; /* Filespec of input file */ + 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; + + 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); + + 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 (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 readErrRow; +static bool readErrVerbose; + +static pm_usererrormsgfn discardMsg; + +static void +discardMsg(const char * const msg) { + if (readErrVerbose) + pm_message("Error reading row %u: %s", readErrRow, msg); +} + + + +static void +countRows(const struct pam * const inpamP, + bool const verbose, + unsigned int * const goodRowCountP) { + + tuple * tuplerow; + unsigned int row; + jmp_buf jmpbuf; + int rc; + unsigned int goodRowCount; + + tuplerow = pnm_allocpamrow(inpamP); + + pm_setusererrormsgfn(discardMsg); + + rc = setjmp(jmpbuf); + if (rc == 0) { + pm_setjmpbuf(&jmpbuf); + + readErrVerbose = verbose; + goodRowCount = 0; /* initial value */ + for (row = 0; row < inpamP->height; ++row) { + readErrRow = row; + pnm_readpamrow(inpamP, tuplerow); + /* The above does not return if it can't read the next row from + the file. Instead, it longjmps out of this loop. + */ + ++goodRowCount; + } + } + *goodRowCountP = goodRowCount; + + pnm_freepamrow(tuplerow); +} + + + +static void +copyGoodRows(const struct pam * const inpamP, + FILE * const ofP, + unsigned int const goodRowCount) { + + struct pam outpam; + tuple * tuplerow; + unsigned int row; + + outpam = *inpamP; /* initial value */ + + outpam.file = ofP; + outpam.height = goodRowCount; + + tuplerow = pnm_allocpamrow(inpamP); + + pnm_writepaminit(&outpam); + + for (row = 0; row < outpam.height; ++row) { + pnm_readpamrow(inpamP, tuplerow); + pnm_writepamrow(&outpam, tuplerow); + } + + pnm_freepamrow(tuplerow); +} + + + +int +main(int argc, char * argv[]) { + struct cmdlineInfo cmdline; + struct pam inpam; + FILE * ifP; + pm_filepos rasterPos; + unsigned int goodRowCount; + + pnm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ifP = pm_openr_seekable(cmdline.inputFilespec); + + pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type)); + + pm_tell2(ifP, &rasterPos, sizeof(rasterPos)); + + countRows(&inpam, cmdline.verbose, &goodRowCount); + + pm_message("Copying %u good rows; %u bottom rows missing", + goodRowCount, inpam.height - goodRowCount); + + pm_seek2(ifP, &rasterPos, sizeof(rasterPos)); + + copyGoodRows(&inpam, stdout, goodRowCount); + + pm_close(inpam.file); + + return 0; +} + + |