| PGM_Reader |

    package ij.plugin;

    import ij.*;
    import ij.io.*;
    import ij.process.*;
    import java.io.*;

    /**
     * This plugin opens PxM format images.
     * <p/>
     * The portable graymap format is a lowest common denominator
     * grayscale file format. The definition is as follows:
     * <p/>
     * - A "magic number" for identifying the  file  type.   A  pgm
     * file's magic number is the two characters "P2".
     * - Whitespace (blanks, TABs, CRs, LFs).
     * - A width, formatted as ASCII characters in decimal.
     * - Whitespace.
     * - A height, again in ASCII decimal.
     * - Whitespace.
     * - The maximum gray value, again in ASCII decimal.
     * - Whitespace.
     * - Width * height gray values, each in ASCII decimal, between
     * 0 and the specified maximum value, separated by whi-
     * tespace, starting at the top-left corner of the graymap,
     * proceeding in normal English reading order. A value of 0
     * means black, and the maximum value means white.
     * - Characters from a "#" to the next end-of-line are ignored (comments).
     * - No line should be longer than 70 characters.
     * <p/>
     * Here is an example of a small graymap in this format:
     * P2
     * # feep.pgm
     * 24 7
     * 15
     * 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
     * 0  3  3  3  3  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15 15 15 15  0
     * 0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0 15  0
     * 0  3  3  3  0  0  0  7  7  7  0  0  0 11 11 11  0  0  0 15 15 15 15  0
     * 0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0  0  0
     * 0  3  0  0  0  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15  0  0  0  0
     * 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
     * <p/>
     * There is a  PGM variant that stores the pixel data as raw bytes:
     * <p/>
     * -The "magic number" is "P5" instead of "P2".
     * -The gray values are stored as plain bytes, instead of ASCII decimal.
     * -No whitespace is allowed in the grays section, and only a single
     * character of whitespace (typically a newline) is allowed after the maxval.
     * -The files are smaller and many times faster to read and write.
     * <p/>
     * Kai Barthel Nov 16 2004:
     * Extended to support PPM (portable pixmap) format images (24 bits only).
     * -The "magic numbers" are "P6" (raw) "P3" (ASCII).
     * <p/>
     * Ulf Dittmer April 2005:
     * Extended to support PBM (bitmap) images (P1 and P4)
     * <p/>
     * Jarek Sacha (jarek.at.ieee.org) December 2005:
     * Extended PPM support to 48 bit color images.
     */

    public class PGM_Reader extends ImagePlus implements PlugIn {

        private int width, height;
        private boolean rawBits;
        private boolean sixteenBits;
        private boolean isColor;
        private boolean isBlackWhite;
        private int maxValue;

        public void run(String arg) {
            OpenDialog od = new OpenDialog("PBM/PGM/PPM Reader...", arg);
            String directory = od.getDirectory();
            String name = od.getFileName();
            if (name == null)
                return;
            String path = directory + name;

            IJ.showStatus("Opening: " + path);
            ImageStack stack;
            try {
                stack = openFile(path);
            }
            catch (IOException e) {
                String msg = e.getMessage();
                IJ.showMessage("PBM/PGM/PPM Reader", msg.equals("") ? "" + e : msg);
                return;
            }
            setStack(name, stack);
            FileInfo fi = new FileInfo();
            fi.fileFormat = FileInfo.PGM;
            fi.directory = directory;
            fi.fileName = name;
            setFileInfo(fi);
            if (arg.equals(""))
            show();
            if (sixteenBits && stack.getSize()==3) {
                WindowManager.setTempCurrentImage(this);
                IJ.run("Convert Stack to RGB");
            }
        }

        public ImageStack openFile(String path) throws IOException {
            InputStream is = new BufferedInputStream(new FileInputStream(path));
            StreamTokenizer tok = new StreamTokenizer(is); //deprecated, but it works
            //Reader r = new BufferedReader(new InputStreamReader(is));
            //StreamTokenizer tok = new StreamTokenizer(r);  // doesn't work
            tok.resetSyntax();
            tok.wordChars(33, 255);
            tok.whitespaceChars(0, ' ');
            tok.parseNumbers();
            tok.eolIsSignificant(true);
            tok.commentChar('#');
            openHeader(tok);
            //IJ.log("PGM_Reader: w="+width+",h="+height+",raw="+rawBits+",16bits="+sixteenBits+",color="+isColor+",b&w="+isBlackWhite+",max="+maxValue);

            if (!isColor && sixteenBits) { // 16-bit grayscale
                if (rawBits) {
                    ImageProcessor ip = open16bitRawImage(is, width, height);
                    ImageStack stack = new ImageStack(width, height);
                    stack.addSlice("", ip);
                    return stack;
                } else {
                    ImageProcessor ip = open16bitAsciiImage(tok, width, height);
                    ImageStack stack = new ImageStack(width, height);
                    stack.addSlice("", ip);
                    return stack;
                }
            }

            if (!isColor) { // 8-bit grayscale
                byte[] pixels = new byte[width * height];
                ImageProcessor ip = new ByteProcessor(width, height, pixels, null);
                if (rawBits)
                    openRawImage(is, width * height, pixels);
                else
                    openAsciiImage(tok, width * height, pixels);
                for (int i = pixels.length - 1; i >= 0; i--) {
                    if (isBlackWhite) {
                        if (rawBits) {
                            if (i < (pixels.length / 8)) {
                                for (int bit = 7; bit >= 0; bit--) {
                                    pixels[8 * i + 7 - bit] = (byte) ((pixels[i] & ((int) Math.pow(2, bit))) == 0 ? 255 : 0);
                                }
                            }
                        } else
                            pixels[i] = (byte) (pixels[i] == 0 ? 255 : 0);
                    } else
                        pixels[i] = (byte) (0xff & (255 * (int) (0xff & pixels[i]) / maxValue));
                }
                ImageStack stack = new ImageStack(width, height);
                stack.addSlice("", ip);
                return stack;
            }

            if (!sixteenBits) { // 8-bit color
                int[] pixels = new int[width * height];
                byte[] bytePixels = new byte[3 * width * height];
                ImageProcessor ip = new ColorProcessor(width, height, pixels);
                if (rawBits)
                    openRawImage(is, 3 * width * height, bytePixels);
                else
                    openAsciiImage(tok, 3 * width * height, bytePixels);
                for (int i = 0; i < width * height; i++) {
                    int r = (int) (0xff & bytePixels[i * 3]);
                    int g = (int) (0xff & bytePixels[i * 3 + 1]);
                    int b = (int) (0xff & bytePixels[i * 3 + 2]);
                    r = (r * 255 / maxValue) << 16;
                    g = (g * 255 / maxValue) << 8;
                    b = (b * 255 / maxValue);
                    pixels[i] = 0xFF000000 | r | g | b;
                }
                ImageStack stack = new ImageStack(width, height);
                stack.addSlice("", ip);
                return stack;
            }

            // 16-bit raw color
            short[] red = new short[width*height];
            short[] green = new short[width*height];
            short[] blue = new short[width*height];
            if (rawBits) {
                byte[] bytePixels = new byte[6*width*height];
                openRawImage(is, 6*width*height, bytePixels);
                for (int i=0; i<width*height; i++) {
                    int r1 = 0xff & bytePixels[i*6];
                    int r2 = 0xff & bytePixels[i*6+1];
                    int g1 = 0xff & bytePixels[i*6+2];
                    int g2 = 0xff & bytePixels[i*6+3];
                    int b1 = 0xff & bytePixels[i*6+ 4];
                    int b2 = 0xff & bytePixels[i*6+5];
                    red[i] = (short) (0xffff & (r1*255+r2));
                    green[i] = (short) (0xffff & (g1*255+g2));
                    blue[i] = (short) (0xffff & (b1*255+b2));
                }
            } else {
                ImageProcessor ip = open16bitAsciiImage(tok, 3*width, height);
                short[] pixels = (short[])ip.getPixels();
                for (int i=0; i<width*height; i++) {
                    red[i] = (short)(pixels[i*3]&0xffffff);
                    green[i] = (short)(pixels[i*3+1]&0xffffff);
                    blue[i] = (short)(pixels[i*3+2]&0xffffff);
                }
            }
            ImageStack stack = new ImageStack(width, height);
            stack.addSlice("red", new ShortProcessor(width, height, red, null));
            stack.addSlice("green", new ShortProcessor(width, height, green, null));
            stack.addSlice("blue", new ShortProcessor(width, height, blue, null));
            return stack;

        }

        public void openHeader(StreamTokenizer tok) throws IOException {
            String magicNumber = getWord(tok);
            if (magicNumber.equals("P1")) {
                rawBits = false;
                isColor = false;
                isBlackWhite = true;
            } else if (magicNumber.equals("P4")) {
                rawBits = true;
                isColor = false;
                isBlackWhite = true;
            } else if (magicNumber.equals("P2")) {
                rawBits = false;
                isColor = false;
                isBlackWhite = false;
            } else if (magicNumber.equals("P5")) {
                rawBits = true;
                isColor = false;
                isBlackWhite = false;
            } else if (magicNumber.equals("P3")) {
                rawBits = false;
                isColor = true;
                isBlackWhite = false;
            } else if (magicNumber.equals("P6")) {
                rawBits = true;
                isColor = true;
                isBlackWhite = false;
            } else
                throw new IOException("PxM files must start with \"P1\" or \"P2\" or \"P3\" or \"P4\" or \"P5\" or \"P6\"");
            width = getInt(tok);
            height = getInt(tok);
            if (width == -1 || height == -1)
                throw new IOException("Error opening PxM header..");
            if (! isBlackWhite) {
                maxValue = getInt(tok);
                if (maxValue == -1)
                    throw new IOException("Error opening PxM header..");
                sixteenBits = maxValue > 255;
            } else
                maxValue = 255;
            if (sixteenBits && maxValue > 65535)
                throw new IOException("The maximum gray value is larger than 65535.");
        }

        public void openAsciiImage(StreamTokenizer tok, int size, byte[] pixels) throws IOException {
            int i = 0;
            int inc = size / 20;
            while (tok.nextToken() != tok.TT_EOF) {
                if (tok.ttype == tok.TT_NUMBER) {
                    pixels[i++] = (byte) (((int) tok.nval) & 255);
                    if (i % inc == 0)
                        IJ.showProgress(0.5 + ((double) i / size) / 2.0);
                }
            }
            IJ.showProgress(1.0);
        }

        public void openRawImage(InputStream is, int size, byte[] pixels) throws IOException {
            int count = 0;
            while (count < size && count >= 0)
                count = is.read(pixels, count, size - count);
        }

        public ImageProcessor open16bitRawImage(InputStream is, int width, int height) throws IOException {
            int size = width * height * 2;
            byte[] bytes = new byte[size];
            int count = 0;
            while (count < size && count >= 0)
                count = is.read(bytes, count, size - count);
            short[] pixels = new short[size / 2];
            for (int i = 0, j = 0; i < size / 2; i++, j += 2)
                pixels[i] = (short) (((bytes[j] & 0xff) << 8) | (bytes[j + 1] & 0xff)); //big endian
            return new ShortProcessor(width, height, pixels, null);
        }

        public ImageProcessor open16bitAsciiImage(StreamTokenizer tok, int width, int height) throws IOException {
            int i = 0;
            int size = width * height;
            int inc = size / 20; // Progress update interval
            short[] pixels = new short[size];
            while (tok.nextToken() != tok.TT_EOF) {
                if (tok.ttype == tok.TT_NUMBER) {
                    pixels[i++] = (short) (((int) tok.nval) & 65535);
                    if (i % inc == 0)
                        IJ.showProgress(0.5 + ((double) i / size) / 2.0);
                }
            }
            IJ.showProgress(1.0);
            return new ShortProcessor(width, height, pixels, null);
        }

        String getWord(StreamTokenizer tok) throws IOException {
            while (tok.nextToken() != tok.TT_EOF) {
                if (tok.ttype == tok.TT_WORD)
                    return tok.sval;
            }
            return null;
        }

        int getInt(StreamTokenizer tok) throws IOException {
            while (tok.nextToken() != tok.TT_EOF) {
                if (tok.ttype == tok.TT_NUMBER)
                    return (int) tok.nval;
            }
            return -1;
        }

    }

| PGM_Reader |