Package net.sourceforge.jiu.codecs

Source Code of net.sourceforge.jiu.codecs.PNMCodec

/*
* PNMCodec
*
* Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2006 Marco Schmidt.
* Copyright (c) 2009 Knut Arild Erstad.
* All rights reserved.
*/

package net.sourceforge.jiu.codecs;

import java.io.DataOutput;
import java.io.InputStream;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import net.sourceforge.jiu.data.BilevelImage;
import net.sourceforge.jiu.data.GrayImage;
import net.sourceforge.jiu.data.Gray16Image;
import net.sourceforge.jiu.data.Gray8Image;
import net.sourceforge.jiu.data.GrayIntegerImage;
import net.sourceforge.jiu.data.IntegerImage;
import net.sourceforge.jiu.data.MemoryBilevelImage;
import net.sourceforge.jiu.data.MemoryGray16Image;
import net.sourceforge.jiu.data.MemoryGray8Image;
import net.sourceforge.jiu.data.MemoryRGB24Image;
import net.sourceforge.jiu.data.MemoryRGB48Image;
import net.sourceforge.jiu.data.PixelImage;
import net.sourceforge.jiu.data.RGB24Image;
import net.sourceforge.jiu.data.RGB48Image;
import net.sourceforge.jiu.data.RGBIndex;
import net.sourceforge.jiu.data.RGBIntegerImage;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.OperationFailedException;
import net.sourceforge.jiu.ops.WrongParameterException;

/**
* A codec to read and write Portable Anymap (PNM) image files.
* This format includes three file types well-known in the Unix world:
* <ul>
* <li>PBM (Portable Bitmap - 1 bit per pixel bilevel image),</li>
* <li>PGM (Portable Graymap - grayscale image) and</li>
* <li>PPM (Portable Pixmap - RGB truecolor image).</li>
* </ul>
* <p>
*
* <h3>Compression</h3>
* The file format only allows for uncompressed storage.
*
* <h3>ASCII mode / binary mode</h3>
* PNM streams can be stored in binary mode or ASCII mode.
* ASCII mode files are text files with numbers representing the pixels.
* They become larger than their binary counterparts, but as they are
* very redundant they can be compressed well with archive programs.
* ASCII PGM and PPM files can have all kinds of maximum sample values,
* thus allowing for arbitrary precision.
* They are not restricted by byte limits.
* PBM streams always have two colors, no matter if they are ASCII or binary.
*
* <h3>Color depth for PGM / PPM</h3>
* <p>
* The header of a PGM and PPM file stores a maximum sample value
* (such a value is not stored for PBM, where the maximum value is always 1).
* When in binary mode, PGM and PPM typically have a maximum sample value of 255,
* which makes PGM 8 bits per pixel and PPM 24 bits per pixel large.
* One sample will be stored as a single byte.
* However, there also exist binary PGM files with a maximum sample value larger than
* 255 and smaller than 65536.
* These files use two bytes per sample, in network byte order (big endian).
* I have yet to see PPM files with that property, but they are of course imagineable.
* 16 bpp
* </p>
*
* <h3>DPI values</h3>
* PNM files cannot store the physical resolution in DPI.
*
* <h3>Number of images</h3>
* Only one image can be stored in a PNM file.
*
* <h3>Usage example - load an image from a PNM file</h3>
* <pre>
* PNMCodec codec = new PNMCodec();
* codec.setFile("test.ppm", CodecMode.LOAD);
* codec.process();
* codec.close();
* PixelImage image = codec.getImage();
* </pre>
*
* <h3>Usage example - save an image to a PNM file</h3>
* <pre>
* PNMCodec codec = new PNMCodec();
* BilevelImage myFax = ...; // initialize
* codec.setImage(myFax);
* codec.setFile("out.pbm", CodecMode.SAVE);
* codec.process();
* codec.close();
* </pre>
*
* @author Marco Schmidt
*/
public class PNMCodec extends ImageCodec
{
  /**
   * Image type constant for images of unknown type.
   */
  public static final int IMAGE_TYPE_UNKNOWN = -1;

  /**
   * Image type constant for bilevel images, stored in PBM files.
   */
  public static final int IMAGE_TYPE_BILEVEL = 0;

  /**
   * Image type constant for grayscale images, stored in PGM files.
   */
  public static final int IMAGE_TYPE_GRAY = 1;

  /**
   * Image type constant for RGB truecolor images, stored in PPM files.
   */
  public static final int IMAGE_TYPE_COLOR = 2;
  private static final String[] IMAGE_TYPE_FILE_EXTENSIONS =
    {".pbm", ".pgm", ".ppm"};
  private Boolean ascii;
  private int columns;
  private int imageType;
  private PushbackInputStream in;
  private DataOutput out;
  private int height;
  private int maxSample;
  private int width;

  /**
   * Attempts to find the appropriate image type by looking at a file's name. 
   * Ignores case when comparing.
   * Returns {@link #IMAGE_TYPE_BILEVEL} for <code>.pbm</code>,
   * {@link #IMAGE_TYPE_GRAY} for <code>.pgm</code> and
   * {@link #IMAGE_TYPE_COLOR} for <code>.ppm</code>.
   * Otherwise, {@link #IMAGE_TYPE_UNKNOWN} is returned.
   * To get a file extension given that you have an image type, use
   * {@link #getTypicalFileExtension}.
   *
   * @param fileName the file name to be examined
   * @return one of the <code>IMAGE_TYPE_xxx</code> constants of this class
   */
  public static int determineImageTypeFromFileName(String fileName)
  {
    if (fileName == null || fileName.length() < 4)
    {
      return IMAGE_TYPE_UNKNOWN;
    }
    String ext = fileName.substring(fileName.length() - 3);
    ext = ext.toLowerCase();
    for (int i = 0; i < IMAGE_TYPE_FILE_EXTENSIONS.length; i++)
    {
      if (IMAGE_TYPE_FILE_EXTENSIONS[i].equals(ext))
      {
        return i;
      }
    }
    return IMAGE_TYPE_UNKNOWN;
  }

  /**
   * Returns if ASCII mode was used for loading an image or will
   * be used to store an image.
   * @return true for ASCII mode, false for binary mode, null if that information is not available
   * @see #setAscii
   */
  public Boolean getAscii()
  {
    return ascii;
  }

  public String getFormatName()
  {
    return "Portable Anymap (PBM, PGM, PPM)";
  }

  public String[] getMimeTypes()
  {
    return new String[] {"image/x-ppm", "image/x-pgm", "image/x-pbm", "image/x-pnm",
      "image/x-portable-pixmap", "image/x-portable-bitmap", "image/x-portable-graymap",
      "image/x-portable-anymap"};
  }

  /**
   * Returns the typical file extension (including leading dot) for an
   * image type.
   * Returns <code>null</code> for {@link #IMAGE_TYPE_UNKNOWN}.
   * To get the image type given that you have a file name, use
   * {@link #determineImageTypeFromFileName}.
   *
   * @param imageType the image type for which the extension is required
   * @return the file extension or null
   */
  public static String getTypicalFileExtension(int imageType)
  {
    if (imageType >= 0 && imageType < IMAGE_TYPE_FILE_EXTENSIONS.length)
    {
      return IMAGE_TYPE_FILE_EXTENSIONS[imageType];
    }
    else
    {
      return null;
    }
  }

  public boolean isLoadingSupported()
  {
    return true;
  }

  public boolean isSavingSupported()
  {
    return true;
  }

  /**
   * Loads an image from a PNM input stream.
   * It is assumed that a stream was given to this codec using {@link #setInputStream(InputStream)}.
   *
   * @return the image as an instance of a class that implements {@link IntegerImage}
   * @throws InvalidFileStructureException if the input stream is not a valid PNM stream (or unsupported)
   * @throws java.io.IOException if there were problems reading from the input stream
   */
  private void load() throws
    InvalidFileStructureException,
    IOException,
    MissingParameterException,
    UnsupportedTypeException,
    WrongFileFormatException,
    WrongParameterException
  {
    InputStream is = getInputStream();
    if (is != null)
    {
      if (is instanceof PushbackInputStream)
      {
        in = (PushbackInputStream)is;
      }
      else
      {
        in = new PushbackInputStream(is);
      }
    }
    else
    {
      throw new MissingParameterException("InputStream object required for loading.");
    }
    loadType();
    String resolutionLine = loadTextLine();
    setResolution(resolutionLine);
    setBoundsIfNecessary(width, height);
    if (imageType == IMAGE_TYPE_BILEVEL)
    {
      maxSample = 1;
    }
    else
    {
      // load maximum value
      String maxSampleLine = loadTextLine();
      setMaximumSample(maxSampleLine);
    }
    if (maxSample > 65535)
    {
      throw new UnsupportedTypeException("Cannot deal with samples larger than 65535.");
    }
    checkImageResolution();
    switch (imageType)
    {
      case(IMAGE_TYPE_BILEVEL):
      {
        loadBilevelImage();
        break;
      }
      case(IMAGE_TYPE_COLOR):
      {
        loadColorImage();
        break;
      }
      case(IMAGE_TYPE_GRAY):
      {
        loadGrayImage();
        break;
      }
      default:
      {
        throw new UnsupportedTypeException("Cannot deal with image type.");
      }
    }
  }

  private int loadAsciiNumber() throws
    InvalidFileStructureException,
    IOException
  {
    boolean hasDigit = false;
    int result = -1;
    do
    {
      int b = in.read();
      if (b >= 48 && b <= 57)
      {
        // decimal digit
        if (hasDigit)
        {
          result = result * 10 + (b - 48);
        }
        else
        {
          hasDigit = true;
          result = b - 48;
        }
      }
      else
      if (b == 32 || b == 10 || b == 13 || b == 9)
      {
        // whitespace
        if (hasDigit)
        {
          if (result > maxSample)
          {
            throw new InvalidFileStructureException("Read number " +
              "from PNM stream that is larger than allowed " +
              "maximum sample value " + maxSample + " (" + result + ").");
          }
          return result;
        }
        // ignore whitespace
      }
      else
      if (b == 35)
      {
        // the # character, indicating a comment row
        if (hasDigit)
        {
          in.unread(b);
          if (result > maxSample)
          {
            throw new InvalidFileStructureException("Read " +
              "number from PNM stream that is larger than " +
              "allowed maximum sample value " + maxSample +
              " (" + result + ").");
          }
          return result;
        }
        do
        {
          b = in.read();
        }
        while (b != -1 && b != 10 && b != 13);
        if (b == 13)
        {
        }
        // put it into the comment list
      }
      else
      if (b == -1)
      {
        // the end of file character
        if (hasDigit)
        {
          if (result > maxSample)
          {
            throw new InvalidFileStructureException("Read number from PNM stream that is larger than allowed maximum sample value " +
              maxSample + " (" + result + ")");
          }
          return result;
        }
        throw new InvalidFileStructureException("Unexpected end of file while reading ASCII number from PNM stream.");
      }
      else
      {
        throw new InvalidFileStructureException("Read invalid character from PNM stream: " + b +
          " dec.");
      }
    }
    while(true);
  }

  private void loadBilevelImage() throws
    InvalidFileStructureException,
    IOException,
    WrongParameterException
  {
    PixelImage image = getImage();
    if (image == null)
    {
      setImage(new MemoryBilevelImage(getBoundsWidth(), getBoundsHeight()));
    }
    else
    {
      if (!(image instanceof BilevelImage))
      {
        throw new WrongParameterException("Specified input image must implement BilevelImage for this image type.");
      }
    }
    if (getAscii().booleanValue())
    {
      loadBilevelImageAscii();
    }
    else
    {
      loadBilevelImageBinary();
    }
  }

  private void loadBilevelImageAscii() throws
    InvalidFileStructureException,
    IOException
  {
    BilevelImage image = (BilevelImage)getImage();
    // skip the pixels of the first getBoundsY1() rows
    int pixelsToSkip = width * getBoundsY1();
    for (int i = 0; i < pixelsToSkip; i++)
    {
      loadAsciiNumber();
    }
    final int NUM_ROWS = getBoundsHeight();
    final int COLUMNS = getBoundsWidth();
    final int X1 = getBoundsX1();
    int[] row = new int[width];
    // now read and store getBoundsHeight() rows
    for (int y = 0; y < NUM_ROWS; y++)
    {
      for (int x = 0; x < width; x++)
      {
        int value = loadAsciiNumber();
        if (value == 0)
        {
          row[x] = BilevelImage.WHITE;
        }
        else
        if (value == 1)
        {
          row[x] = BilevelImage.BLACK;
        }
        else
        {
          throw new InvalidFileStructureException("Loaded " +
            "number for position x=" + x + ", y=" + (y + getBoundsY1()) +
            " is neither 0 nor 1 in PBM stream: " + value);
        }
      }
      image.putSamples(0, 0, y, COLUMNS, 1, row, X1);
      setProgress(y, NUM_ROWS);
    }
  }

  private void loadBilevelImageBinary() throws
    InvalidFileStructureException,
    IOException
  {
    BilevelImage image = (BilevelImage)getImage();
    int bytesPerRow = (width + 7) / 8;
    // skip the first getBoundsY1() rows
    long bytesToSkip = (long)getBoundsY1() * (long)bytesPerRow;
    // Note:
    // removed in.skip(bytesToSkip) because that was only available in Java 1.2
    // instead the following while loop is used
    while (bytesToSkip-- > 0)
    {
      in.read();
    }
    // allocate buffer large enough for a complete row
    byte[] row = new byte[bytesPerRow];
    final int numRows = getBoundsHeight();
    // read and store the next getBoundsHeight() rows
    for (int y = 0; y < numRows; y++)
    {
      // read bytesPerRow bytes into row
      int bytesToRead = bytesPerRow;
      int index = 0;
      while (bytesToRead > 0)
      {
        int result = in.read(row, index, bytesToRead);
        if (result >= 0)
        {
          index += result;
          bytesToRead -= result;
        }
        else
        {
          throw new InvalidFileStructureException("Unexpected end of input stream while reading.");
        }
      }
      // invert values
      for (int x = 0; x < row.length; x++)
      {
        row[x] = (byte)~row[x];
      }
      //image.putPackedBytes(0, y, bytesPerRow, buffer, 0);
      if (isRowRequired(y))
      {
        image.putPackedBytes(0, y - getBoundsY1(), getBoundsWidth(), row, getBoundsX1() >> 3, getBoundsX1() & 7);
      }
      setProgress(y, numRows);
    }
  }

  /**
   * Read a 16-bit binary value in network (big-endian) order.
   * @return An integer between 0 and 65535, or -1 for EOF.
   * @throws IOException If the underlying read operation failed.
   */
  private int read16BitBinaryValue() throws IOException {
    int byte1 = in.read();
    if (byte1 < 0)
      return -1;
    int byte2 = in.read();
    if (byte2 < 0)
      return -1;
    return (byte1 << 8) | byte2;
  }

  private void loadColorImage() throws InvalidFileStructureException, IOException
  {
    RGBIntegerImage image = null;
    RGB24Image image24 = null;
    RGB48Image image48 = null;
    if (maxSample <= 255)
    {
      image24 = new MemoryRGB24Image(width, height);
      image = image24;
      setImage(image);
    }
    else
    {
      image48 = new MemoryRGB48Image(width, height);
      image = image48;
      setImage(image);
    }
    for (int y = 0, destY = - getBoundsY1(); y < height; y++, destY++)
    {
      if (getAscii().booleanValue())
      {
        for (int x = 0; x < width; x++)
        {
          int red = loadAsciiNumber();
          if (red < 0 || red > maxSample)
          {
            throw new InvalidFileStructureException("Invalid " +
              "sample value " + red + " for red sample at " +
              "(x=" + x + ", y=" + y + ").");
          }
          image.putSample(RGBIndex.INDEX_RED, x, y, red);

          int green = loadAsciiNumber();
          if (green < 0 || green > maxSample)
          {
            throw new InvalidFileStructureException("Invalid " +
              "sample value " + green + " for green sample at " +
              "(x=" + x + ", y=" + y + ").");
          }
          image.putSample(RGBIndex.INDEX_GREEN, x, y, green);

          int blue = loadAsciiNumber();
          if (blue < 0 || blue > maxSample)
          {
            throw new InvalidFileStructureException("Invalid " +
              "sample value " + blue + " for blue sample at " +
              "(x=" + x + ", y=" + y + ").");
          }
          image.putSample(RGBIndex.INDEX_BLUE, x, y, blue);
        }
      }
      else
      {
        if (image24 != null)
        {
          for (int x = 0; x < width; x++)
          {
            int red = in.read();
            if (red == -1)
            {
              throw new InvalidFileStructureException("Unexpected " +
                "end of file while reading red sample for pixel " +
                "x=" + x + ", y=" + y + ".");
            }
            image24.putByteSample(RGBIndex.INDEX_RED, x, y, (byte)(red & 0xff));
            int green = in.read();
            if (green == -1)
            {
              throw new InvalidFileStructureException("Unexpected " +
                "end of file while reading green sample for pixel " +
                "x=" + x + ", y=" + y + ".");
            }
            image24.putByteSample(RGBIndex.INDEX_GREEN, x, y, (byte)(green & 0xff));
            int blue = in.read();
            if (blue == -1)
            {
              throw new InvalidFileStructureException("Unexpected " +
                "end of file while reading blue sample for pixel " +
                "x=" + x + ", y=" + y + ".");
            }
            image24.putByteSample(RGBIndex.INDEX_BLUE, x, y, (byte)(blue & 0xff));
          }
        }
        else if (image48 != null)
        {
          for (int x = 0; x < width; x++)
          {
            int red = read16BitBinaryValue();
            if (red == -1)
            {
              throw new InvalidFileStructureException("Unexpected " +
                "end of file while reading red sample for pixel " +
                "x=" + x + ", y=" + y + ".");
            }
            image48.putShortSample(RGBIndex.INDEX_RED, x, y, (short)(red & 0xffff));
            int green = read16BitBinaryValue();
            if (green == -1)
            {
              throw new InvalidFileStructureException("Unexpected " +
                "end of file while reading green sample for pixel " +
                "x=" + x + ", y=" + y + ".");
            }
            image48.putShortSample(RGBIndex.INDEX_GREEN, x, y, (short)(green & 0xffff));
            int blue = read16BitBinaryValue();
            if (blue == -1)
            {
              throw new InvalidFileStructureException("Unexpected " +
                "end of file while reading blue sample for pixel " +
                "x=" + x + ", y=" + y + ".");
            }
            image48.putShortSample(RGBIndex.INDEX_BLUE, x, y, (short)(blue & 0xffff));
          }
        }
      }
      setProgress(y, getBoundsHeight());
    }
  }

  private void loadGrayImage() throws InvalidFileStructureException, IOException, UnsupportedTypeException
  {
    final int WIDTH = getBoundsWidth();
    final int HEIGHT = getBoundsHeight();
    PixelImage pimage = getImage();
    if (pimage == null)
    {
      if (maxSample < 256)
      {
        pimage = new MemoryGray8Image(WIDTH, HEIGHT);
      }
      else
      if (maxSample < 65536)
      {
        pimage = new MemoryGray16Image(WIDTH, HEIGHT);
      }
      else
      {
        throw new UnsupportedTypeException("Gray images with more than 16 bits per pixel are not supported.");
      }
      setImage(pimage);
    }
    else
    {
    }
    GrayIntegerImage image = (GrayIntegerImage)pimage;
    int[] buffer = new int[width];
    for (int y = 0, destY = -getBoundsY1(); destY < getBoundsHeight(); y++, destY++)
    {
      if (getAscii().booleanValue())
      {
        for (int x = 0; x < width; x++)
        {
          buffer[x] = loadAsciiNumber();
        }
      }
      else
      {
        if (maxSample < 256)
        {
          for (int x = 0; x < width; x++)
          {
            buffer[x] = in.read();
          }
        }
        else
        {
          for (int x = 0; x < width; x++)
          {
            int msb = in.read();
            int lsb = in.read();
            buffer[x] = (msb << 8) | lsb;
          }
        }
      }
      if (destY >= 0 && destY < getBoundsHeight())
      {
        image.putSamples(0, 0, destY, getBoundsWidth(), 1, buffer, getBoundsX1());
      }
      setProgress(y, getBoundsY2() + 1);
    }
  }

  private String loadTextLine() throws InvalidFileStructureException, IOException
  {
    // load text lines until
    // 1) a normal text line is found
    // 2) an error occurs
    // any comment lines starting with # are added to the
    // comments Vector
    boolean isComment;
    StringBuffer sb;
    do
    {
      sb = new StringBuffer();
      int b;
      boolean crOrLf;
      do
      {
        b = in.read();
        if (b == -1)
        {
          throw new InvalidFileStructureException("Unexpected end of file in PNM stream.");
        }
        crOrLf = (b == 0x0a || b == 0x0d);
        if (!crOrLf)
        {
          sb.append((char)b);
        }
      }
      while (!crOrLf);
      if (b == 0x0d)
      {
        b = in.read();
        if (b != 0x0a)
        {
          throw new InvalidFileStructureException("Unexpected end of file in PNM stream.");
        }
      }
      isComment = (sb.length() > 0 && sb.charAt(0) == '#');
      if (isComment)
      {
        //sb.deleteCharAt(0);
        //sb.delete(0, 1);
        StringBuffer result = new StringBuffer(sb.length() - 1);
        int i = 1;
        while (i < sb.length())
        {
          result.append(sb.charAt(i++));
        }
        appendComment(result.toString());
      }
    }
    while (isComment);
    return sb.toString();
  }

  /**
   * Loads the first two characters (which are expected to be a capital P
   * followed by a decimal digit between 1 and 6, inclusively) and skips
   * following LF and CR characters.
   * This method not only checks the two bytes, it also initializes internal fields
   * for storage mode (ASCII or binary) and image type.
   *
   * @throws WrongFileFormatException if the input stream is not a PNM stream
   * @throws InvalidFileStructureException if the format that
   *  is described above was not encountered
   * @throws java.io.IOException if there were errors reading data
   * @throws java.lang.IllegalArgumentException if the input stream was not given to this codec
   */
  private void loadType() throws InvalidFileStructureException, IOException, WrongFileFormatException
  {
    // read two bytes
    int v1 = in.read();
    int v2 = in.read();
    // check if first byte is P
    if (v1 != 0x50)
    {
      throw new WrongFileFormatException("Not a PNM stream. First byte " +
        "in PNM stream is expected to be 0x50 ('P'); found: " +
        v1 + " (dec).");
    }
    // check if second byte is ASCII of digit from 1 to 6
    if (v2 < 0x31 || v2 > 0x36)
    {
      throw new WrongFileFormatException("Not a PNM stream. Second byte " +
        "in PNM stream is expected to be the ASCII value of decimal " +
        "digit between 1 and 6 (49 dec to 54 dec); found " +
        v2 + " dec.");
    }
    // determine mode (ASCII or binary) from second byte
    ascii = new Boolean(v2 < 0x34);
    // determine image type from second byte
    v2 = v2 - 0x30;
    imageType = (v2 - 1) % 3;
    // skip LF and CR
    int b;
    do
    {
      b = in.read();
    }
    while (b == 0x0a || b == 0x0d || b == ' ');
    if (b == -1)
    {
      throw new InvalidFileStructureException("Read type (" +
        v2 + "). Unexpected end of file in input PNM stream.");
    }
    in.unread(b);
  }

  public void process() throws
    MissingParameterException,
    OperationFailedException
  {
    initModeFromIOObjects();
    try
    {
      if (getMode() == CodecMode.LOAD)
      {
        load();
      }
      else
      {
        save();
      }
    }
    catch (IOException ioe)
    {
      throw new OperationFailedException("I/O error: " + ioe.toString());
    }
  }

  private void save() throws
    IOException,
    MissingParameterException,
    WrongParameterException
  {
    out = getOutputAsDataOutput();
    if (out == null)
    {
      throw new WrongParameterException("Cannot get a DataOutput object to use for saving.");
    }
    PixelImage pi = getImage();
    if (pi == null)
    {
      throw new MissingParameterException("Input image missing.");
    }
    if (!(pi instanceof IntegerImage))
    {
      throw new WrongParameterException("Input image must implement IntegerImage.");
    }
    IntegerImage image = (IntegerImage)pi;
    width = image.getWidth();
    height = image.getHeight();
    setBoundsIfNecessary(width, height);
    if (image instanceof RGB24Image)
    {
      imageType = IMAGE_TYPE_COLOR;
      maxSample = 255;
      save((RGB24Image)image);
    }
    else
    if (image instanceof RGB48Image)
    {
      imageType = IMAGE_TYPE_COLOR;
      maxSample = 65535;
      save((RGB48Image)image);
    }
    else
    if (image instanceof BilevelImage)
    {
      imageType = IMAGE_TYPE_BILEVEL;
      maxSample = 1;
      save((BilevelImage)image);
    }
    else
    if (image instanceof Gray8Image)
    {
      imageType = IMAGE_TYPE_GRAY;
      maxSample = 255;
      save((Gray8Image)image);
    }
    else
    if (image instanceof Gray16Image)
    {
      imageType = IMAGE_TYPE_GRAY;
      maxSample = 65535;
      save((Gray16Image)image);
    }
    else
    {
      throw new WrongParameterException("Unsupported input image type: " +
        image.getClass().getName());
    }
    close();
  }

  private void save(BilevelImage image) throws IOException
  {
    saveHeader();
    final int WIDTH = getBoundsWidth();
    final int HEIGHT = getBoundsHeight();
    final int BYTES_PER_ROW = (WIDTH + 7) / 8;
    byte[] buffer = new byte[BYTES_PER_ROW];
    for (int y = 0, srcY = getBoundsY1(); y < HEIGHT; y++, srcY++)
    {
      if (getAscii().booleanValue())
      {
        for (int x = 0, srcX = getBoundsX1(); x < WIDTH; x++, srcX++)
        {
          if (image.isBlack(srcX, srcY))
          {
            out.write(49); // 1
          }
          else
          {
            out.write(48); // 0
          }
          columns ++;
          if (columns > 70)
          {
            columns = 0;
            out.write(10);
          }
          else
          {
            out.write(32);
            columns++;
          }
        }
      }
      else
      {
        image.getPackedBytes(getBoundsX1(), srcY, WIDTH, buffer, 0, 0);
        for (int x = 0; x < buffer.length; x++)
        {
          buffer[x] = (byte)(~buffer[x]);
        }
        out.write(buffer);
      }
      setProgress(y, HEIGHT);
    }
  }

  private void save(Gray8Image image) throws IOException
  {
    saveHeader();
    final int HEIGHT = getBoundsHeight();
    final int WIDTH = getBoundsWidth();
    final int X1 = getBoundsX1();
    System.out.println(WIDTH + " " + HEIGHT + " " + X1);
    byte[] buffer = new byte[WIDTH];
    for (int y = 0, srcY = getBoundsY1(); y < HEIGHT; y++, srcY++)
    {
      image.getByteSamples(0, X1, srcY, WIDTH, 1, buffer, 0);
      if (getAscii().booleanValue())
      {
        for (int x = 0; x < WIDTH; x++)
        {
          saveAsciiNumber(buffer[x] & 0xff);
          out.write(32);
          columns += 2;
          if (columns > 70)
          {
            columns = 0;
            out.write(10);
          }
          else
          {
            out.write(32);
            columns++;
          }
        }
      }
      else
      {
        out.write(buffer);
      }
      setProgress(y, HEIGHT);
    }
  }

  private void save(Gray16Image image) throws IOException
  {
    saveHeader();
    final int HEIGHT = getBoundsHeight();
    final int WIDTH = getBoundsWidth();
    final int X1 = getBoundsX1();
    short[] buffer = new short[WIDTH];
    for (int y = 0, srcY = getBoundsY1(); y < HEIGHT; y++, srcY++)
    {
      image.getShortSamples(0, X1, srcY, WIDTH, 1, buffer, 0);
      if (getAscii().booleanValue())
      {
        for (int x = 0; x < WIDTH; x++)
        {
          saveAsciiNumber(buffer[x] & 0xffff);
          out.write(32);
          columns += 4;
          if (columns > 70)
          {
            columns = 0;
            out.write(10);
          }
          else
          {
            out.write(32);
            columns++;
          }
        }
      }
      else
      {
        for (int x = 0; x < WIDTH; x++)
        {
          int sample = buffer[x] & 0xffff;
          out.write((sample >> 8) & 0xff);
          out.write(sample & 0xff);
        }
      }
      setProgress(y, HEIGHT);
    }
  }

  private void save(RGB24Image image) throws IOException
  {
    saveHeader();
    final int WIDTH = getBoundsWidth();
    final int HEIGHT = getBoundsHeight();
    for (int y = 0, srcY = getBoundsY1(); y < HEIGHT; y++, srcY++)
    {
      if (getAscii().booleanValue())
      {
        for (int x = 0, srcX = getBoundsX1(); x < WIDTH; x++, srcX++)
        {
          int red = image.getSample(RGBIndex.INDEX_RED, srcX, srcY);
          int green = image.getSample(RGBIndex.INDEX_GREEN, srcX, srcY);
          int blue = image.getSample(RGBIndex.INDEX_BLUE, srcX, srcY);
          saveAsciiNumber(red);
          out.write(32);
          saveAsciiNumber(green);
          out.write(32);
          saveAsciiNumber(blue);
          columns += 11;
          if (columns > 80)
          {
            columns = 0;
            out.write(10);
          }
          else
          {
            out.write(32);
            columns++;
          }
        }
      }
      else
      {
        for (int x = 0, srcX = getBoundsX1(); x < WIDTH; x++, srcX++)
        {
          out.write(image.getSample(RGBIndex.INDEX_RED, srcX, srcY));
          out.write(image.getSample(RGBIndex.INDEX_GREEN, srcX, srcY));
          out.write(image.getSample(RGBIndex.INDEX_BLUE, srcX, srcY));
        }
      }
      setProgress(y, HEIGHT);
    }
  }

  private void save(RGB48Image image) throws IOException
  {
    saveHeader();
    final int WIDTH = getBoundsWidth();
    final int HEIGHT = getBoundsHeight();
    for (int y = 0, srcY = getBoundsY1(); y < HEIGHT; y++, srcY++)
    {
      if (getAscii().booleanValue())
      {
        for (int x = 0, srcX = getBoundsX1(); x < WIDTH; x++, srcX++)
        {
          int red = image.getSample(RGBIndex.INDEX_RED, srcX, srcY);
          int green = image.getSample(RGBIndex.INDEX_GREEN, srcX, srcY);
          int blue = image.getSample(RGBIndex.INDEX_BLUE, srcX, srcY);
          saveAsciiNumber(red);
          out.write(32);
          saveAsciiNumber(green);
          out.write(32);
          saveAsciiNumber(blue);
          columns += 13;
          if (columns > 80)
          {
            columns = 0;
            out.write(10);
          }
          else
          {
            out.write(32);
            columns++;
          }
        }
      }
      else
      {
        /*
        for (int x = 0, srcX = getBoundsX1(); x < WIDTH; x++, srcX++)
        {
          out.write(image.getSample(RGBIndex.INDEX_RED, srcX, srcY));
          out.write(image.getSample(RGBIndex.INDEX_GREEN, srcX, srcY));
          out.write(image.getSample(RGBIndex.INDEX_BLUE, srcX, srcY));
        }
        */
      }
      setProgress(y, HEIGHT);
    }
  }

  private void saveAsciiNumber(int number) throws
    IOException
  {
    String s = Integer.toString(number);
    for (int i = 0; i < s.length(); i++)
    {
      char c = s.charAt(i);
      out.write(c);
    }
    columns += s.length();
  }

  private void saveHeader() throws IOException
  {
    out.write(80); // 'P'
    int pnmType = 49 + imageType;
    if (getAscii() == null)
    {
      setAscii(maxSample > 255);
    }
    if (!getAscii().booleanValue())
    {
      pnmType += 3;
    }
    out.write(pnmType); // '1' .. '6'
    out.write(10); // line feed
    saveAsciiNumber(getBoundsWidth());
    out.write(32); // space
    saveAsciiNumber(getBoundsHeight());
    out.write(10); // line feed
    if (imageType != IMAGE_TYPE_BILEVEL)
    {
      // bilevel max sample is always 1 and MUST NOT be saved
      saveAsciiNumber(maxSample);
      out.write(10);// line feed
    }
  }

  /**
   * Specify whether ASCII mode is to be used when saving an image.
   * Default is binary mode.
   * @param asciiMode if true, ASCII mode is used, binary mode otherwise
   */
  public void setAscii(boolean asciiMode)
  {
    ascii = new Boolean(asciiMode);
  }

  private void setMaximumSample(String line) throws InvalidFileStructureException
  {
    line = line.trim();
    try
    {
      maxSample = Integer.parseInt(line);
    }
    catch (NumberFormatException nfe)
    {
      throw new InvalidFileStructureException("Not a valid value for the maximum sample: " + line);
    }
    if (maxSample < 0)
    {
      throw new InvalidFileStructureException("The value for the maximum sample must not be negative; found " + maxSample);
    }
  }

  /*
   * Reads resolution from argument String and sets private variables
   * width and height.
   */
  private void setResolution(String line) throws InvalidFileStructureException
  {
    line = line.trim();
    StringTokenizer st = new StringTokenizer(line, " ");
    try
    {
      if (!st.hasMoreTokens())
      {
        throw new InvalidFileStructureException("No width value found in line \"" +
          line + "\".");
      }
      String number = st.nextToken();
      try
      {
        width = Integer.parseInt(number);
      }
      catch (NumberFormatException nfe)
      {
        throw new InvalidFileStructureException("Not a valid int value for width: " +
          number);
      }
      if (width < 1)
      {
        throw new InvalidFileStructureException("The width value must be larger than " +
          "zero; found " + width + ".");
      }
      if (!st.hasMoreTokens())
      {
        throw new InvalidFileStructureException("No height value found in line \"" +
          line + "\".");
      }
      number = st.nextToken();
      try
      {
        height = Integer.parseInt(number);
      }
      catch (NumberFormatException nfe)
      {
        throw new InvalidFileStructureException("Not a valid int value for height: " +
          number);
      }
      if (height < 1)
      {
        throw new InvalidFileStructureException("The height value must be larger than " +
          "zero; found " + width + ".");
      }
    }
    catch (NoSuchElementException nsee)
    {
      // should not happen because we always check if there is a token
    }
  }

  public String suggestFileExtension(PixelImage image)
  {
    if (image == null)
    {
      return null;
    }
    if (image instanceof BilevelImage)
    {
      return IMAGE_TYPE_FILE_EXTENSIONS[IMAGE_TYPE_BILEVEL];
    }
    else
    if (image instanceof GrayImage)
    {
      return IMAGE_TYPE_FILE_EXTENSIONS[IMAGE_TYPE_GRAY];
    }
    else
    if (image instanceof RGB24Image)
    {
      return IMAGE_TYPE_FILE_EXTENSIONS[IMAGE_TYPE_COLOR];
    }
    return null;
  }
}
TOP

Related Classes of net.sourceforge.jiu.codecs.PNMCodec

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.