Home Manual Packages Global Index Keywords Quick Reference |
    /*
       PNM.I

       Read and write PBM, PGM, or PPM image files defined by the PBMPLUS
       package by Jef Poskanzer.  The most recent version of this package
       (as of 16 Apr 1996) is called NetPBM and is available from:
       * wuarchive.wustl.edu (128.252.135.4),
         directory /graphics/graphics/packages/NetPBM
       * ikaros.fysik4.kth.se (130.237.35.2), directory /pub/netpbm.
       * ftp.informatik.uni-oldenburg.de (134.106.1.9). This site also carries
         binaries for the Amiga.
       * peipa.essex.ac.uk (155.245.115.161), directory ipa/src/manip
       * ftp.rahul.net (192.160.13.1), directory /pub/davidsen/source
       * ftp.cs.ubc.ca, directory /ftp/archive/netpbm
       Among the converters that come with this package are GIF, FITS,
       PICT, and TIFF.  Many, many other formats are supported.

       See also the Independent JPEG group software for conversion to and
       from JFIF (JPEG) files.  The home archive site is:
          ftp.uu.net:/graphics/jpeg/
       E-mail jpeg-info@uunet.uu.net for more information.

       $Id$
     */
    /*    Copyright (c) 1996.  The Regents of the University of California.
                        All rights reserved.  */

    local pnm ;
    /* DOCUMENT #include "pnm.i"

         makes pnm_read and pnm_write functions available, which see.
     */

    /* ------------------------------------------------------------------------ */

    func pnm_read (filename, noflip)

    /* DOCUMENT image= pnm_read(filename)

             or image= pnm_read(filename, noflip)

         read a PBM, PGM, or PPM image from FILENAME.  PBM and PGM files

         result in width-by-height arrays; PPM files result in 3-by-width-

         by-height arrays.  If NOFLIP is present and non-zero, the returned
         image is in exactly the order stored in FILENAME.  Otherwise, the

         height dimension is reversed, since images in the files read top
         to bottom, while the Yorick image plotting commands go bottom to
         top.

       SEE ALSO: pnm_display, pnm_write
     */
    {
      f= open(filename, "rb");
      magic= array('\0', 2);

      if (_read(f,0,magic)<2 || magic(1)!='P' ||
          magic(2)<'1' || magic(2)>'6')

        error, "not a PBM, PGM, or PPM file: "+filename;
      magic= long(magic(2)-'0');
      raw= magic>3;
      magic%= 3;
      if (raw) {
        image= _pnm_rawrd(f, magic);
      } else {
        /* reopen f as a text file for text PNM magic numbers */
        f=open(filename);
        image= _pnm_txtrd(f, magic);
      }
      if (!noflip) image= image(..,::-1);
      return image;
    }

    func _pnm_txtrd (f, magic)
    {
      /* read text PNM */
      line= rdline(f);
      tok= strtok(line);
      line= tok(2);
      tok= tok(1);
      if (strlen(tok)!=2)

        error, "garbled PNM header in "+filename;

      width= height= maxcol= 0;
      if (magic==1) maxcol= 1;  /* maxcol not present in PBM */

      while (!width || !height || !maxcol) {
        tok= strtok(line);
        line= tok(2);
        tok= tok(1);

        if (tok && strpart(tok,1:1)!="#") {
          n= 0;

          if (sread(tok,format="%ld",n)!=1 || n<=0)
    	error, "bad PNM header in "+filename;

          if (!width) width= n;

          else if (!height) height= n;
          else if (!maxcol) maxcol= n;
        } else {
          line= rdline(f);
        }

        if (!strlen(line)) line= rdline(f);
      }

      /* construct result, treating PBM as PGM since text PBM is
         one character per pixel */

      image= _pnm_image(width, height, maxcol, magic+(magic==1));

      /* PGM and PPM files may be read as ordinary numbers
       * PBM files may have no whitespace between 0s and 1s */
      if (magic!=1) format= "%d";
      else format= "%1d";

      /* allow image to start on same line as maxcol - yuck */

      n= sread(line, format=format, image);

      img= array(structof(image), numberof(image)-n);
      while (numberof(image)>n) {
        m= read(f, format=format, img);
        if (!m) {

          /* don't distinguish between legal comments with # and

           * illegal garbage characters that halt read */

          if (!rdline(f)) error, "early end-of-file in "+filename;
          backup, f;
          continue;
        }
        image(n+1:n+m)= img(1:m);
        n+= m;
      }

      return image;
    }

    func _pnm_rawrd (f, magic)
    {

      /* read binary PNM -- much faster than text */

      addr= height= width= maxcol= 0;
      if (magic==1) maxcol= 1;
      _pnm_token, f, addr;
      if (addr!=3 ||

          sread(_pnm_token(f,addr), width)!=1 || width<=0 ||

          sread(_pnm_token(f,addr), height)!=1 || height<=0 ||

          (!maxcol && sread(_pnm_token(f,addr), maxcol)!=1) || maxcol<=0)

        error, "bad PNM header in "+filename;
      if (maxcol>255)

        error, "max color > 255 in raw PNM file "+filename;

      image= _pnm_image(width, height, maxcol, magic);
      _read, f, addr, image;

      return _pnm_image(image, width, height);
    }

    func _pnm_token (f, &addr)
    {
      /* binary PNM token finder must be extremely robust, since file
         may have come from a different architecture and must have been
         transmitted in binary mode */

      line= array(char, 80);  /* lines guaranteed less than 70 chars */
      for (;;) {
        n= _read(f, addr, line);

        if (!n) error, "end-of-file in PNM header in "+filename;
        if (n<80) line= line(1:n);
        i= 1;
        c= line(i);
        while ((c==' '||c=='\t') && i<n) c= line(++i);

        if (i==n) error, "PNM header line too long in "+filename;
        if (c=='\012' || c=='\015') {
          addr+= 1;
        } else {
          addr+= i-1;
          if (i>1) line= line(i:n);
          if (c=='#') {

    	list= where((line=='\012') | (line=='\015'));
    	if (!numberof(list))

    	  error, "PNM header line too long in "+filename;
    	addr+= list(1);
          } else {
    	list= where(line<=' ');
    	if (!numberof(list))

    	  error, "PNM header line too long in "+filename;
    	list= list(1);
    	addr+= list;
    	return string(&line(1:list));
          }
        }
      }
    }

    func _pnm_image (width, height, maxcol, magic)
    {

      task= dimsof(width)(1);
      if (task) {
        /* reconstruct binary PBM from packed bits */
        if (task>1) return width;

        image= array(char, height, maxcol);
        n= numberof(image);
        rem= n%8;
        image(1:n:8)= width>>7;

        if (rem==1) width= width(1:-1);
        image(2:n:8)= width>>6;

        if (rem==2) width= width(1:-1);
        image(3:n:8)= width>>5;

        if (rem==3) width= width(1:-1);
        image(4:n:8)= width>>4;

        if (rem==4) width= width(1:-1);
        image(5:n:8)= width>>3;

        if (rem==5) width= width(1:-1);
        image(6:n:8)= width>>2;

        if (rem==6) width= width(1:-1);
        image(7:n:8)= width>>1;

        if (rem==7) width= width(1:-1);
        image(8:n:8)= width;
        width= [];
        return image&'\1';
      }

      /* construct result array - binary PBM stored as packed bits */

      if (magic==1) return array(char, (width*height+7)/8);
      if (maxcol<256) type= char;

      else if (maxcol<32768) type= short;
      else type= long;

      if (magic==2) return array(type, width, height);

      return array(type, 3, width, height);
    }

    /* ------------------------------------------------------------------------ */

    func pnm_display (image, size=, flip=)
    /* DOCUMENT pnm_display, image

         Attempt to display the IMAGE with the pli command and a pseudo-color

         palette.  The IMAGE must be a 3-by-width-by-height array of RGB
         pixel values.  If called as a function, the return value is the

         width-by-height array of pixels which index into the new palette.

         The palette is returned as the external variables red, green, and

         blue.  The new palette is both coarse and slow to compute.

         The size= keyword can be used to set the palette size.  The default
         is 200 colors, the size of all the distribution palettes.

         The flip=1 keyword can be used to flip the image bottom for top.

         The square=1 limits flag is set; use
            limits,square=0

         to return to the default non-square plot limits.

       SEE ALSO: pnm_read
     */
    {
      if (is_void(size)) size= 200;
      image= bytscl(image,top=255);

      r= image(1,,);
      g= image(2,,);
      b= image(3,,);

      /* count cells in 16x16x16 subcubes of full RGB color cube */
      rgb= (long(r>>4) | (g&0xf0) | ((b&0xf0)<<4)) + 1;
      hist= histogram(rgb,top=4096);

      /* the size available colors must be shared among occupied subcubes
         -- allocate them in order of occupation */
      order= map= sort(-hist);
      map(order)= indgen(0:4095);
      c= order-1;
      cr= char(c<<4)+'\10';
      cg= char(c&0xf0)+'\10';
      cb= char((c>>4)&0xf0)+'\10';

      extern red, green, blue;
      red= cr(1:size);
      green= cg(1:size);
      blue= cb(1:size);

      map= char(map);
      pal= long([red,green,blue])(,-,);
      pic= long([cr,cg,cb])(-,,);
      for (i=size+1 ; i<=4096 && hist(order(i)) ; i=k+1) {
        k= min(i+7, 4096);

        map(order(i:k))= ((pal-pic(,i:k,))^2)(,,sum)(mnx,)-1;
      }

      rgb= map(rgb);
      if (flip) rgb= rgb(,::-1);

      fma;
      limits, square=1;
      palette, red, green, blue;
      pli, rgb;

      return rgb;
    }

    /* ------------------------------------------------------------------------ */

    func pnm_write (image, filename, noflip, text=, noscale=)

    /* DOCUMENT pnm_write, image, filename)

             or pnm_write, image, filename, noflip)

         write IMAGE to a PBM, PGM, or PPM file called FILENAME.  If
         NOFLIP is present and non-zero, the image in FILENAME is stored

         in exactly the order of IMAGE in memory.  Otherwise, the height

         dimension is reversed, since images in the files read top to
         bottom, while the Yorick image plotting commands go bottom to
         top.

         If IMAGE is a 3-by-width-by-height array, a PPM will be written.

         Otherwise, IMAGE must be a width-by-height array.  If IMAGE
         contains at most two distinct values, a PBM will be written,

         otherwise a PGM.  If IMAGE is of type char, it will be used as

         is, otherwise it will be scaled to the range 0 to 255.

         If the text= keyword is present and non-zero, a text PNM file

         will be written; the default is to write a binary or raw PNM.

         If the noscale= keyword is supplied, the IMAGE will not be

         scaled to the range 0 to 255.  In this case, the IMAGE must

         have an integer data type with minimum value >=0.  Note that

         if max(image)>255, a text PGM or PPM file will be written
         (overriding the text= keyword).  Raw PNM is char-only.

       SEE ALSO: pnm_colorize, pnm_read
     */
    {
      /* condition input image */
      dims= dimsof(image);
      if (dims(1)==2) {
        width= dims(2);
        height= dims(3);
      } else if (dims(1)==3 && dims(2)==3) {
        width= dims(3);
        height= dims(4);
        color= 1;
      } else {

        error, "image must be w-by-h (bw) or 3-by-w-by-h (color) array";
      }
      if (noscale) {
        if (structof(image(1)+0)!=long)

          error, "noscale= image must have integer data type";
        else if (min(image)<0)

          error, "noscale= image must be positive everywhere";
        maxcol= int(max(image));
        if (maxcol>255) text= 1;
      } else {
        maxcol= 255n;
      }
      if (structof(image)!=char) {

        if (!noscale) image= bytscl(image, top=255);
      } else {
        maxcol= int(max(image));
      }
      if (!noscale) {
        itest= (image!='\0');
        /* be careful! char is unsigned! */

        if (max(char(maxcol*itest)-image)==0) {
          maxcol= 1n;
          image= char(itest);
        }
      }
      if (!noflip) image= image(..,::-1);

      if (maxcol==1) magic= 1;
      else if (!color) magic= 2;
      else magic= 3;
      if (!text) magic+= 3;

      header= swrite(format="P%ld\n%ld %ld\n%s", magic, width, height,

    		 (maxcol>1? pr1(maxcol)+"\n" : ""));

      if (text) {
        f= create(filename);
        write, f, format="%s", header;
        if (maxcol==1) format= "%1d";
        else format= " %d";

        write, f, linesize=70, format=format, image;

        write, f, "";  /* be sure there is a newline at end */

      } else {
        if (maxcol==1) {
          /* pack raw PBM images 8 bits per char */
          image= image(*);
          rem= numberof(image)%8;

          if (rem) image= grow(image,array(char,8-rem));

          image= (image(1::8)<<7) | (image(2::8)<<6) | (image(3::8)<<5) |
    	(image(4::8)<<4) | (image(5::8)<<3) | (image(6::8)<<2) |
    	  (image(7::8)<<4) | image(8::8);
        }
        f= open(filename, "wb");

        _write, f, 0, (*pointer(header))(1:-1);
        addr= strlen(header);
        _write, f, addr, image;

        remove, filename+"L";  /* get rid of contents log file */
      }
    }

    /* ------------------------------------------------------------------------ */

    func pnm_colorize (image, cmin=, cmax=, top=)

    /* DOCUMENT color_image= pnm_colorize(image)

         colorize the width-by-height array IMAGE, returning a 3-by-

         width-by-height array, suitable for use with pnm_write.

         The current graphics palette is used to perform the colorization.
         The keywords cmin=, cmax=, and top= are recognized and have the

         same meaning as for the plf, pli, and bytscl functions.

       SEE ALSO: pnm_write, bytscl
     */
    {
      if (structof(image)!=char)

        image= bytscl(image, cmin=cmin, cmax=cmax, top=top);

      local r, g, b;
      palette, query=1, r,g,b;
      rgb= transpose([r,g,b]);

      return rgb(,image);
    }

    /* ------------------------------------------------------------------------ */