/*
 * @author Douglas A. Lyon
 * @version  Oct 12, 2002.10:53:37 AM
 */
package ip.ppm;


import java.io.IOException;
import java.io.InputStream;


// PpmDecoder - read in a PPM image
//
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/





/// Read in a PPM image.
// <P>
// <A HREF="/resources/classes/Acme/JPM/Decoders/PpmDecoder.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
// <P>
// @see Acme.JPM.Encoders.PpmEncoder

public class ReadPPM extends ImageDecoder {

  /// Constructor.
  // @param in The stream to read the bytes from.
  public ReadPPM(InputStream in) {
    super(in);
  }


  private int type;
  private static final int PBM_ASCII = 1;
  private static final int PGM_ASCII = 2;
  private static final int PPM_ASCII = 3;
  private static final int PBM_RAW = 4;
  private static final int PGM_RAW = 5;
  private static final int PPM_RAW = 6;

  private int width = -1, height = -1;
  private int maxval;

  /// Subclasses implement this to read in enough of the image stream
  // to figure out the width and height.
  void readHeader(InputStream in) throws IOException {
    char c1, c2;

    c1 = (char) readByte(in);
    c2 = (char) readByte(in);

    if (c1 != 'P')
      throw new IOException("not a PBM/PGM/PPM file");
    switch (c2) {
      case '1':
        type = PBM_ASCII;
        break;
      case '2':
        type = PGM_ASCII;
        break;
      case '3':
        type = PPM_ASCII;
        break;
      case '4':
        type = PBM_RAW;
        break;
      case '5':
        type = PGM_RAW;
        break;
      case '6':
        type = PPM_RAW;
        break;
      default:
        throw new IOException("not a standard PBM/PGM/PPM file");
    }
    width = readInt(in);
    height = readInt(in);
    if (type != PBM_ASCII && type != PBM_RAW)
      maxval = readInt(in);
  }

  /// Subclasses implement this to return the width, or -1 if not known.
  int getWidth() {
    return width;
  }

  /// Subclasses implement this to return the height, or -1 if not known.
  int getHeight() {
    return height;
  }

  /// Subclasses implement this to read pixel data into the rgbRow
  // array, an int[width].  One int per pixel, no offsets or padding,
  // RGBdefault (AARRGGBB) color model
  void readRow(InputStream in, int row, int[] rgbRow) throws IOException {
    int col, r, g, b;
    int rgb = 0;
    char c;

    for (col = 0; col < width; ++col) {
      switch (type) {
        case PBM_ASCII:
          c = readChar(in);
          if (c == '1')
            rgb = 0xff000000;
          else if (c == '0')
            rgb = 0xffffffff;
          else
            throw new IOException("illegal PBM bit");
          break;
        case PGM_ASCII:
          g = readInt(in);
          rgb = makeRgb(g, g, g);
          break;
        case PPM_ASCII:
          r = readInt(in);
          g = readInt(in);
          b = readInt(in);
          rgb = makeRgb(r, g, b);
          break;
        case PBM_RAW:
          if (readBit(in))
            rgb = 0xff000000;
          else
            rgb = 0xffffffff;
          break;
        case PGM_RAW:
          g = readByte(in);
          if (maxval != 255)
            g = fixDepth(g);
          rgb = makeRgb(g, g, g);
          break;
        case PPM_RAW:
          r = readByte(in);
          g = readByte(in);
          b = readByte(in);
          if (maxval != 255) {
            r = fixDepth(r);
            g = fixDepth(g);
            b = fixDepth(b);
          }
          rgb = makeRgb(r, g, b);
          break;
      }
      rgbRow[col] = rgb;
    }
  }

  /// Utility routine to read a byte.  Instead of returning -1 on
  // EOF, it throws an exception.
  private static int readByte(InputStream in) throws IOException {
    int b = in.read();
    if (b == -1)
      throw new IOException();
    return b;
  }

  private int bitshift = -1;
  private int bits;

  /// Utility routine to read a bit, packed eight to a byte, big-endian.
  private boolean readBit(InputStream in) throws IOException {
    if (bitshift == -1) {
      bits = readByte(in);
      bitshift = 7;
    }
    boolean bit = (((bits >> bitshift) & 1) != 0);
    --bitshift;
    return bit;
  }

  /// Utility routine to read a character, ignoring comments.
  private static char readChar(InputStream in) throws IOException {
    char c;

    c = (char) readByte(in);
    if (c == '#') {
      do {
        c = (char) readByte(in);
      } while (c != '\n' && c != '\r');
    }

    return c;
  }

  /// Utility routine to read the first non-whitespace character.
  private static char readNonwhiteChar(InputStream in) throws IOException {
    char c;

    do {
      c = readChar(in);
    } while (c == ' ' || c == '\t' || c == '\n' || c == '\r');

    return c;
  }

  /// Utility routine to read an ASCII integer, ignoring comments.
  private static int readInt(InputStream in) throws IOException {
    char c;
    int i;

    c = readNonwhiteChar(in);
    if (c < '0' || c > '9')
      throw new IOException("junk in file where integer should be");

    i = 0;
    do {
      i = i * 10 + c - '0';
      c = readChar(in);
    } while (c >= '0' && c <= '9');

    return i;
  }

  /// Utility routine to rescale a pixel value from a non-eight-bit maxval.
  private int fixDepth(int p) {
    return (p * 255 + maxval / 2) / maxval;
  }

  /// Utility routine make an RGBdefault pixel from three color values.
  private static int makeRgb(int r, int g, int b) {
    return 0xff000000 | (r << 16) | (g << 8) | b;
  }

}