//******************************************************************************
//
// File:    PJGImage.java
// Package: edu.rit.image
// Unit:    Class edu.rit.image.PJGImage
//
// This Java source file is copyright (C) 2008 by Alan Kaminsky. All rights
// reserved. For further information, contact the author, Alan Kaminsky, at
// ark@cs.rit.edu.
//
// This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
// software; you can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// PJ is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// A copy of the GNU General Public License is provided in the file gpl.txt. You
// may also obtain a copy of the GNU General Public License on the World Wide
// Web at http://www.gnu.org/licenses/gpl.html.
//
//******************************************************************************

package edu.rit.image;

import edu.rit.swing.Displayable;

import edu.rit.util.Range;

import java.awt.image.BufferedImage;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;

/**
 * Class PJGImage is the abstract base class for an image that is read from or
 * written to a file in Parallel Java Graphics (PJG) format. PJG image files are
 * designed to be generated by parallel programs.
 * 

* Compared to PNG files, PJG files use a compression algorithm that yields * somewhat larger file sizes but has a much smaller running time. In one test * of a set of 24-bit color images, the PJG file sizes were 2-17% of the raw * data sizes, whereas the PNG file sizes were 2-11% of the raw data sizes. The * PJG file sizes were 30-50% larger than the corresponding PNG file sizes. * However, the time to write the PJG files was only 1/10th to 1/20th the time * to write the PNG files. This small running time to write a PJG file can * substantially reduce a parallel program's sequential fraction, with a * corresponding increase in the program's scalability. *

* In addition, sections of an image can be written to a PJG image file in * arbitrary order. Sections of an image can also be scattered across multiple * files. This lets the processes of a cluster parallel program generate * portions of an image independently, without needing to gather the whole image * into one process. *

* The {@linkplain PJG} program can be used to display an image stored in one * PJG file or scattered among multiple PJG files; to save the displayed image * in a single PJG file; and to save the image in a PNG file or PostScript file. *

* Usage *

* To create a PJGImage object, instantiate one of the subclasses of class * PJGImage, depending on the type of image. The image's pixel data is stored in * an external matrix, as described in the documentation for each subclass. The * available subclasses are: *

* Subclasses for other types of images will be added in later releases. *

* To get and set the image's pixel data, use methods in the subclass. You only * need to allocate storage in the pixel data matrix for the portions of the * image you are actually accessing; the complete matrix need not be allocated. * Class {@linkplain edu.rit.util.Arrays} has static methods for allocating * portions of a matrix. *

* To write a PJGImage object to a PJG image file, call the * prepareToWrite() method, specifying the output stream to write. The * prepareToWrite() method returns an instance of class {@linkplain * PJGImage.Writer}. Call the methods of the PJG image writer object to write * the pixel data, or sections of the pixel data, to the output stream. When * finished, close the PJG image writer. *

* To read a PJGImage object from a PJG image file, call the * prepareToRead() method, specifying the input stream to read. The * prepareToRead() method returns an instance of class {@linkplain * PJGImage.Reader}. Call the methods of the PJG image reader object to read * the pixel data, or sections of the pixel data, from the input stream. When * finished, close the PJG image reader. *

* The previous paragraph assumes you already have a PJGImage object which is an * instance of the correct subclass to hold the pixel data that will be read * from the PJG image file. To both create an instance of the correct PJGImage * subclass and read a PJG image file into that instance, call the static * readFromStream() method. To create an instance of the correct * PJGImage subclass to hold the contents of a PJG image file without actually * reading in the pixel data, call the static createFromStream() * method. *

* To get a BufferedImage object that uses the same underlying pixel data matrix * as the PJGImage object, call the getBufferedImage() method. You can * then do all the following with the BufferedImage: display it on the screen, * draw into it using a graphics context, copy another BufferedImage into it, * read it from or write it to a file using package javax.imageio (which * typically supports PNG, JPG, and GIF formats). The rows and columns of the * underlying matrix need not all be allocated when accessing the BufferedImage. * If you get a pixel from the BufferedImage in an unallocated row or column, a * pixel value of 0 (black) is returned. If you set a pixel in the BufferedImage * in an unallocated row or column, the pixel value is discarded. *

* Note: Class PJGImage is not multiple thread safe. *

* PJG Image File Format *

* A PJG image file consists of a sequence of segments. Each segment * consists of the following: *

*

* Header segment (segment type 0). The first segment in the file must be * a header segment. There must be exactly one header segment in the file. The * segment contents are: *

* Thus, the first eight bytes of a PJG image file must be 0x00, 0x50, 0x4A, * 0x47, 0x00, 0x00, 0x00, 0x01. *

* Image type segment (segment type 1). The image type segment must come * after the header segment and before any pixel data segments. There must be * exactly one image type segment in the file. The segment contents are: *

*

* Height segment (segment type 2). The height segment must come after * the header segment and before any pixel data segments. There must be exactly * one height segment in the file. The segment contents are: *

*

* Width segment (segment type 3). The width segment must come after the * header segment and before any pixel data segments. There must be exactly one * width segment in the file. The segment contents are: *

*

* Creation time segment (segment type 4). The creation time segment must * come after the header segment and before any pixel data segments. There must * be at most one creation time segment in the file. The segment contents are: *

*

* Comment segment (segment type 5). A comment segment must come after * the header segment and before any pixel data segments. There may be zero or * more comment segments in the file. The segment contents are: *

*

* Run length encoded 24-bit color pixel data segment (segment type 6). A * run length encoded 24-bit color pixel data segment may appear only in a file * with image type = 0 (24-bit color image). A run length encoded 24-bit color * pixel data segment may appear anywhere in the file after the header, image * type, height, width, creation time, and comment segments. There may be zero * or more run length encoded 24-bit color pixel data segments in the file. *

* The pixel data segment contains pixel data for one block of the image. * The block encompasses certain rows and certain columns of the image (not * necessarily all the rows and columns). Blocks may appear in any order in the * file. The file need not contain pixel data for all the rows and columns in * the image. *

* There is an implicit dictionary that associates consecutive indexes 0, * 1, 2, . . . with pixel data values. The dictionary is not stored in the file; * rather, the contents of the dictionary are built up as the pixel data * segments are written or read. The dictionary persists between pixel data * segments and is used for all the pixel data segments in the file. *

* The first four fields of the pixel data segment are: *

* The pixel data for the block is encoded as a sequence of runs. Each * run consists of one or more consecutive pixels with identical values. The * runs are found by scanning the pixel rows from lowest to highest row index. * Within each row, the runs are found by scanning the pixels from lowest to * highest column index. A run ends at the end of a row, or when a different * pixel value is encountered. *

* Each run is encoded as follows. Let n be one less than the number of * pixels in the run. Let p be the 24-bit pixel value (8-bit red * component, 8-bit green component, 8-bit blue component) of the pixels in the * run. Let x be the index associated with pixel value p in the * dictionary. First encode the run length: *

* Then encode the pixel value: * *

* Huffman delta encoded 8-bit grayscale pixel data segment (segment type * 7). A Huffman delta encoded 8-bit grayscale pixel data segment may appear * only in a file with image type = 1 (8-bit grayscale image). A Huffman delta * encoded 8-bit grayscale pixel data segment may appear anywhere in the file * after the header, image type, height, width, creation time, and comment * segments. There may be zero or more Huffman delta encoded 8-bit grayscale * pixel data segments in the file. *

* The pixel data segment contains pixel data for one block of the image. * The block encompasses certain rows and certain columns of the image (not * necessarily all the rows and columns). Blocks may appear in any order in the * file. The file need not contain pixel data for all the rows and columns in * the image. *

* The first four fields of the pixel data segment are: *

* Each pixel value is an integer from 0 (black) through 255 (white) inclusive. *

* Certain pixel data in each row of the block is encoded in the form of * deltas rather than the pixel values themselves. The delta in column * i is equal to the pixel value in column i minus the pixel value * in column i-1, except the delta in the block's first column is equal * to the pixel value in the block's first column. *

* The pixel data for the block is encoded by scanning the pixel rows from * lowest to highest row index. Within each row, the pixels are scanned from * lowest to highest column index. Each column is encoded using a modified * Huffman encoding as follows: *

* The above encoding process yields a bit string. Each group of 8 bits is * packed into a byte, from most significant bit to least significant bit. Any * unused least significant bits in the last byte are set to zero. The resulting * sequence of bytes forms the remainder of the pixel data segment. *

* Huffman delta encoded 24-bit hue pixel data segment (segment type 8). * A Huffman delta encoded 24-bit hue pixel data segment may appear only in a * file with image type = 2 (24-bit hue image). A Huffman delta encoded 24-bit * hue pixel data segment may appear anywhere in the file after the header, * image type, height, width, creation time, and comment segments. There may be * zero or more Huffman delta encoded 24-bit hue pixel data segments in the * file. *

* The pixel data segment contains pixel data for one block of the image. * The block encompasses certain rows and certain columns of the image (not * necessarily all the rows and columns). Blocks may appear in any order in the * file. The file need not contain pixel data for all the rows and columns in * the image. *

* The first four fields of the pixel data segment are: *

* Each 24-bit pixel value consists of an 8-bit red component, an 8-bit green * component, and an 8-bit blue component. Each component is an integer from 0 * through 255 inclusive. *

* First, the red components of all the pixels are treated as an 8-bit grayscale * image and are encoded using Huffman delta encoding as described above, * yielding a bit string. *

* Second, the green components of all the pixels are treated as an 8-bit * grayscale image and are encoded using Huffman delta encoding as described * above, yielding a bit string. *

* Third, the blue components of all the pixels are treated as an 8-bit * grayscale image and are encoded using Huffman delta encoding as described * above, yielding a bit string. *

* Finally, the above three bit strings are concatenated yielding a longer bit * string. Each group of 8 bits is packed into a byte, from most significant bit * to least significant bit. Any unused least significant bits in the last byte * are set to zero. The resulting sequence of bytes forms the remainder of the * pixel data segment. * * @author Alan Kaminsky * @version 08-Apr-2008 */ public abstract class PJGImage { // Hidden constants. static final byte[] HEADER = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0x4A, (byte) 0x47, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, }; static final int SEGMENT_HEADER = 0; static final int SEGMENT_IMAGE_TYPE = 1; static final int SEGMENT_HEIGHT = 2; static final int SEGMENT_WIDTH = 3; static final int SEGMENT_CREATION_TIME = 4; static final int SEGMENT_COMMENT = 5; static final int SEGMENT_PIXEL_DATA_RLE_24_BIT_COLOR = 6; static final int SEGMENT_PIXEL_DATA_HDE_8_BIT_GRAY = 7; static final int SEGMENT_PIXEL_DATA_HDE_24_BIT_HUE = 8; static final int IMAGE_TYPE_24_BIT_COLOR = 0; static final int IMAGE_TYPE_8_BIT_GRAY = 1; static final int IMAGE_TYPE_24_BIT_HUE = 2; // Hidden data members. int myImageType; int myHeight; int myWidth; Long myCreationTime; LinkedList myComments = new LinkedList(); // Hidden constructors. /** * Construct a new PJG image. The image's height and width are * uninitialized. Before accessing the image's pixels, specify the height * and width by calling a subclass method or by reading the image from an * input stream. * * @param theImageType Image type. */ PJGImage (int theImageType) { myImageType = theImageType; } // Exported operations. /** * Returns this image's height. * * @return Image height in pixels. */ public int getHeight() { return myHeight; } /** * Returns this image's width. * * @return Image width in pixels. */ public int getWidth() { return myWidth; } /** * Returns this image's creation time. * * @return Creation time in milliseconds since midnight 01-Jan-1970 UTC, or * null if creation time is not set. */ public Long getCreationTime() { return myCreationTime; } /** * Set this image's creation time. * * @param time Creation time in milliseconds since midnight 01-Jan-1970 * UTC. If null, the creation time is not set. */ public void setCreationTime (Long time) { myCreationTime = time; } /** * Returns an iterable collection of the comments in this image. The * returned collection is unmodifiable. * * @return Comment strings. */ public Iterable getComments() { return Collections.unmodifiableList (myComments); } /** * Remove all of the comments in this image. */ public void clearComments() { myComments.clear(); } /** * Add the given comment to this image. * * @param theComment Comment string. * * @exception NullPointerException * (unchecked exception) Thrown if theComment is null. */ public void addComment (String theComment) { myComments.add (theComment); } /** * Prepare to write this image to the given output stream. Certain header * information is written to the output stream at this time. To write this * image's pixel data, call methods on the returned PJG image writer, then * close the PJG image writer. *

* For improved performance, specify an output stream with buffering, such * as an instance of class java.io.BufferedOutputStream. * * @param theStream Output stream. * * @return PJG image writer object with which to write this image. * * @exception NullPointerException * (unchecked exception) Thrown if theStream is null. * @exception IOException * Thrown if an I/O error occurred. */ public abstract PJGImage.Writer prepareToWrite (OutputStream theStream) throws IOException; /** * Prepare to read this image from the given input stream. Certain header * information is read from the input stream at this time. To read this * image's pixel data, call methods on the returned PJG image reader, then * close the PJG image reader. *

* For improved performance, specify an input stream with buffering, such * as an instance of class java.io.BufferedInputStream. * * @param theStream Input stream. * * @return PJG image reader object with which to read this image. * * @exception NullPointerException * (unchecked exception) Thrown if theStream is null. * @exception IOException * Thrown if an I/O error occurred. */ public abstract PJGImage.Reader prepareToRead (InputStream theStream) throws IOException; /** * Create an instance of class PJGImage based on the given input stream. An * instance of the correct subclass of class PJGImage is created, depending * on the contents of the input stream. The image's header information is * read from the input stream, then the input stream is reset to its * position when createFromStream() was called. The input stream is * not closed. *

* The createFromStream() method calls the mark() and * reset() methods on the input stream. If the input stream does * not support these methods, an IOException is thrown. *

* For improved performance, specify an input stream with buffering, such * as an instance of class java.io.BufferedInputStream. (BufferedInputStream * supports mark() and reset().) * * @param theStream Input stream. * * @return PJG image object. * * @exception NullPointerException * (unchecked exception) Thrown if theStream is null. * @exception IOException * Thrown if an I/O error occurred. */ public static PJGImage createFromStream (InputStream theStream) throws IOException { // Mark input stream. Assume we will need to read at most 100 bytes to // determine the image type. if (! theStream.markSupported()) { throw new IOException ("PJGImage.createImage(): Input stream does not support mark() and reset()"); } theStream.mark (100); // Read and verify header segment. DataInputStream dis = new DataInputStream (theStream); byte[] hdr = new byte [HEADER.length]; dis.readFully (hdr); if (! Arrays.equals (hdr, HEADER)) { throw new PJGImageFileFormatException ("Invalid PJG header"); } // Read segments until we have the image type. int imageType = -1; while (imageType == -1) { int segmentType = dis.readUnsignedByte(); switch (segmentType) { case SEGMENT_IMAGE_TYPE: imageType = dis.readUnsignedByte(); break; case SEGMENT_HEIGHT: dis.readInt(); break; case SEGMENT_WIDTH: dis.readInt(); break; case SEGMENT_CREATION_TIME: dis.readLong(); break; case SEGMENT_COMMENT: dis.readUTF(); break; } } // Restore stream to original position. theStream.reset(); // Return an instance of the correct subclass. switch (imageType) { case IMAGE_TYPE_24_BIT_COLOR: return new PJGColorImage(); case IMAGE_TYPE_8_BIT_GRAY: return new PJGGrayImage(); case IMAGE_TYPE_24_BIT_HUE: return new PJGHueImage(); default: throw new PJGImageFileFormatException ("Invalid PJG image type (= " + imageType + ")"); } } /** * Create an instance of class PJGImage and read the image's pixel data from * the given input stream. An instance of the correct subclass of class * PJGImage is created, depending on the contents of the input stream. The * image's pixel data is read from the input stream, then the input stream * is closed. *

* The readFromStream() method calls the mark() and * reset() methods on the input stream. If the input stream does * not support these methods, an IOException is thrown. *

* For improved performance, specify an input stream with buffering, such * as an instance of class java.io.BufferedInputStream. (BufferedInputStream * supports mark() and reset().) * * @param theStream Input stream. * * @return PJG image object containing image read from theStream. * * @exception NullPointerException * (unchecked exception) Thrown if theStream is null. * @exception IOException * Thrown if an I/O error occurred. */ public static PJGImage readFromStream (InputStream theStream) throws IOException { // Create image. PJGImage image = createFromStream (theStream); // Read pixel data. PJGImage.Reader reader = image.prepareToRead (theStream); reader.read(); reader.close(); // Return image. return image; } /** * Obtain a BufferedImage whose pixel data comes from this image's * underlying matrix. * * @return BufferedImage. */ public abstract BufferedImage getBufferedImage(); /** * Obtain a Displayable object with which to display this image in a Swing * UI. * * @return Displayable object. */ public abstract Displayable getDisplayable(); // Hidden operations. /** * Set this image's height and width. * * @param theHeight Image height in pixels. * @param theWidth Image width in pixels. * * @exception IllegalArgumentException * (unchecked exception) Thrown if theHeight <= 0. Thrown if * theWidth <= 0. */ void setHeightAndWidth (int theHeight, int theWidth) { if (theHeight <= 0) { throw new IllegalArgumentException ("PJGImage.setHeightAndWidth(): theHeight = " + theHeight + " illegal"); } if (theWidth <= 0) { throw new IllegalArgumentException ("PJGImage.setHeightAndWidth(): theWidth = " + theWidth + " illegal"); } myHeight = theHeight; myWidth = theWidth; } /** * Write this image's header, image type, height, width, creation time, and * comment segments to the given data output stream. * * @param theDos Data output stream. * * @exception IOException * Thrown if an I/O error occurred. */ void writeHeader (DataOutputStream theDos) throws IOException { // Header segment. theDos.write (HEADER); // Image type segment. theDos.writeByte (SEGMENT_IMAGE_TYPE); theDos.writeByte (myImageType); // Height segment. theDos.writeByte (SEGMENT_HEIGHT); theDos.writeInt (myHeight); // Width segment. theDos.writeByte (SEGMENT_WIDTH); theDos.writeInt (myWidth); // Creation time segment. if (myCreationTime != null) { theDos.writeByte (SEGMENT_CREATION_TIME); theDos.writeLong (myCreationTime); } // Comment segments. for (String comment : myComments) { theDos.writeByte (SEGMENT_COMMENT); theDos.writeUTF (comment); } } /** * Read the header segment from the given data input stream. If the header * is not correct, an exception is thrown. * * @param theDis Data input stream. * * @return Segment type of the next segment after the header segment, or -1 * if at end of stream. * * @exception IOException * Thrown if an I/O error occurred. */ int readHeader (DataInputStream theDis) throws IOException { // Read and verify header segment. byte[] hdr = new byte [HEADER.length]; theDis.readFully (hdr); if (! Arrays.equals (hdr, HEADER)) { throw new PJGImageFileFormatException ("Invalid PJG header"); } // Next segment type. return theDis.read(); } /** * Read the image type segment from the given data input stream. If the * value read from the input stream does not equal this image's image type, * an exception is thrown. * * @param theDis Data input stream. * * @return Segment type of the next segment after the image type segment, * or -1 if at end of stream. * * @exception IOException * Thrown if an I/O error occurred. */ int readImageType (DataInputStream theDis) throws IOException { // Read and verify image type segment. int t = theDis.readUnsignedByte(); if (t != myImageType) { throw new PJGImageFileFormatException ("Invalid PJG image type: required = " + myImageType + ", found = " + t); } // Next segment type. return theDis.read(); } /** * Read the height segment from the given data input stream. If this image's * height is uninitialized, this image's height is set to the value read * from the input stream. If this image's height is initialized, and the * value read from the input stream is not the same, an exception is thrown. * * @param theDis Data input stream. * * @return Segment type of the next segment after the height segment, or -1 * if at end of stream. * * @exception IOException * Thrown if an I/O error occurred. */ int readHeight (DataInputStream theDis) throws IOException { // Read and verify image height segment. int h = theDis.readInt(); if (h <= 0) { throw new PJGImageFileFormatException ("Invalid PJG image height (= " + h + ")"); } if (myHeight == 0) { myHeight = h; } else if (h != myHeight) { throw new PJGImageFileFormatException ("Invalid PJG image height: required = " + myHeight + ", found = " + h); } // Next segment type. return theDis.read(); } /** * Read the width segment from the given data input stream. If this image's * width is uninitialized, this image's width is set to the value read * from the input stream. If this image's width is initialized, and the * value read from the input stream is not the same, an exception is thrown. * * @param theDis Data input stream. * * @return Segment type of the next segment after the width segment, or -1 * if at end of stream. * * @exception IOException * Thrown if an I/O error occurred. */ int readWidth (DataInputStream theDis) throws IOException { // Read and verify image width segment. int w = theDis.readInt(); if (w <= 0) { throw new PJGImageFileFormatException ("Invalid PJG image width (= " + w + ")"); } if (myWidth == 0) { myWidth = w; } else if (w != myWidth) { throw new PJGImageFileFormatException ("Invalid PJG image width: required = " + myWidth + ", found = " + w); } // Next segment type. return theDis.read(); } /** * Read the creation time segment from the given data input stream. * * @param theDis Data input stream. * * @return Segment type of the next segment after the creation time * segment, or -1 if at end of stream. * * @exception IOException * Thrown if an I/O error occurred. */ int readCreationTime (DataInputStream theDis) throws IOException { // Read and verify image creation time segment. myCreationTime = theDis.readLong(); // Next segment type. return theDis.read(); } /** * Read the comment segment from the given data input stream. * * @param theDis Data input stream. * * @return Segment type of the next segment after the comment segment, or * -1 if at end of stream. * * @exception IOException * Thrown if an I/O error occurred. */ int readComment (DataInputStream theDis) throws IOException { // Read and verify image comment segment. myComments.add (theDis.readUTF()); // Next segment type. return theDis.read(); } // Exported helper classes. /** * Class PJGImage.Writer is the abstract base class for an object with which * to write a {@linkplain PJGImage} to an output stream. *

* When a PJG image writer is created, the header, image type, height, * width, creation time, and comment segments are written to the output * stream. Each time the write(), writeRowSlice(), * writeColSlice(), or writePatch() method is called, one * pixel data segment is written to the output stream. The type of pixel * data segment depends on the PJG image subclass. When finished, call the * close() method. *

* Note: Class PJGImage.Writer is not multiple thread safe. * * @author Alan Kaminsky * @version 31-Oct-2007 */ public abstract class Writer { // Hidden data members. OutputStream myOs; DataOutputStream myDos; // Hidden constructors. /** * Construct a new PJG image writer. * * @param theStream Output stream. * * @exception NullPointerException * (unchecked exception) Thrown if theStream is null. * @exception IOException * Thrown if an I/O error occurred. */ Writer (OutputStream theStream) throws IOException { if (theStream == null) { throw new NullPointerException ("PJGImage.Writer(): theStream is null"); } myOs = theStream; myDos = new DataOutputStream (theStream); writeHeader (myDos); } // Exported operations. /** * Write all rows and columns of the image to the output stream. * * @exception IOException * Thrown if an I/O error occurred. */ public abstract void write() throws IOException; /** * Write the given row slice of the image to the output stream. Pixels * in the given range of rows and in all columns are written. *

* Note: theRowRange's stride must be 1. * * @param theRowRange Range of pixel rows. * * @exception NullPointerException * (unchecked exception) Thrown if theRowRange is null. * @exception IllegalArgumentException * (unchecked exception) Thrown if theRowRange's stride is * greater than 1. * @exception IndexOutOfBoundsException * (unchecked exception) Thrown if any index in theRowRange * is outside the range 0 .. image height - 1. * @exception IOException * Thrown if an I/O error occurred. */ public abstract void writeRowSlice (Range theRowRange) throws IOException; /** * Write the given column slice of the image to the output stream. * Pixels in all rows and in the given range of columns are written. *

* Note: theColRange's stride must be 1. * * @param theColRange Range of pixel columns. * * @exception NullPointerException * (unchecked exception) Thrown if theColRange is null. * @exception IllegalArgumentException * (unchecked exception) Thrown if theColRange's stride is * greater than 1. * @exception IndexOutOfBoundsException * (unchecked exception) Thrown if any index in theColRange * is outside the range 0 .. image width - 1. * @exception IOException * Thrown if an I/O error occurred. */ public abstract void writeColSlice (Range theColRange) throws IOException; /** * Write the given patch of the image to the output stream. Pixels in * the given range of rows and in the given range of columns are * written. *

* Note: theRowRange's stride must be 1. * theColRange's stride must be 1. * * @param theRowRange Range of pixel rows. * @param theColRange Range of pixel columns. * * @exception NullPointerException * (unchecked exception) Thrown if theRowRange is null. * Thrown if theColRange is null. * @exception IllegalArgumentException * (unchecked exception) Thrown if theRowRange's stride is * greater than 1. Thrown if theColRange's stride is * greater than 1. * @exception IndexOutOfBoundsException * (unchecked exception) Thrown if any index in theRowRange * is outside the range 0 .. image height - 1. Thrown if any index * in theColRange is outside the range 0 .. image width - * 1. * @exception IOException * Thrown if an I/O error occurred. */ public abstract void writePatch (Range theRowRange, Range theColRange) throws IOException; /** * Close the output stream. * * @exception IOException * Thrown if an I/O error occurred. */ public void close() throws IOException { myDos.close(); } } /** * Class PJGImage.Reader is the abstract base class for an object with which * to read a {@linkplain PJGImage} from an input stream. *

* When a PJG image reader is created, the header, image type, height, * width, creation time, and comment segments are read from the input * stream. *

* To read the pixel data segments one at a time, first call the * getRowRange() and getColRange() methods to obtain the * range of rows and columns in the next pixel data segment. At this point, * allocate storage for the rows and columns in the underlying matrix if * necessary. Then call the readSegment() method to read the actual * pixel data. Repeat these steps if there are additional pixel data * segments. *

* To read all the pixel data segments (or all the remaining pixel data * segments), call the read() method. *

* When finished, call the close() method. *

* Note: Class PJGImage.Reader is not multiple thread safe. * * @author Alan Kaminsky * @version 01-Nov-2007 */ public abstract class Reader { // Hidden data members. InputStream myIs; DataInputStream myDis; int myNextSegmentType; // Hidden constructors. /** * Construct a new PJG image reader. * * @param theStream Input stream. * * @exception NullPointerException * (unchecked exception) Thrown if theStream is null. * @exception IOException * Thrown if an I/O error occurred. */ Reader (InputStream theStream) throws IOException { if (theStream == null) { throw new NullPointerException ("PJGImage.Reader(): theStream is null"); } myIs = theStream; myDis = new DataInputStream (theStream); // Read and verify segments down to first pixel data segment. boolean imageTypeSeen = false; boolean heightSeen = false; boolean widthSeen = false; boolean creationTimeSeen = false; myNextSegmentType = readHeader (myDis); segmentLoop : for (;;) { switch (myNextSegmentType) { case SEGMENT_IMAGE_TYPE: if (imageTypeSeen) { throw new PJGImageFileFormatException ("Invalid PJG file: multiple image type segments"); } myNextSegmentType = readImageType (myDis); imageTypeSeen = true; break; case SEGMENT_HEIGHT: if (heightSeen) { throw new PJGImageFileFormatException ("Invalid PJG file: multiple height segments"); } myNextSegmentType = readHeight (myDis); heightSeen = true; break; case SEGMENT_WIDTH: if (widthSeen) { throw new PJGImageFileFormatException ("Invalid PJG file: multiple width segments"); } myNextSegmentType = readWidth (myDis); widthSeen = true; break; case SEGMENT_CREATION_TIME: if (creationTimeSeen) { throw new PJGImageFileFormatException ("Invalid PJG file: multiple creation time segments"); } myNextSegmentType = readCreationTime (myDis); creationTimeSeen = true; break; case SEGMENT_COMMENT: myNextSegmentType = readComment (myDis); break; default: break segmentLoop; } } if (! imageTypeSeen) { throw new PJGImageFileFormatException ("Invalid PJG file: missing image type segment"); } if (! heightSeen) { throw new PJGImageFileFormatException ("Invalid PJG file: missing height segment"); } if (! widthSeen) { throw new PJGImageFileFormatException ("Invalid PJG file: missing width segment"); } } // Exported operations. /** * Read all pixel data segments from the input stream. If some pixel * data segments have already been read, the read() method * reads all remaining pixel data segments. If there are no more pixel * data segments, the read() method does nothing. If storage is * not already allocated in the underlying matrix for the pixel rows and * columns in a pixel data segment, the read() method allocates * the necessary storage. * * @exception IOException * Thrown if an I/O error occurred. */ public abstract void read() throws IOException; /** * Obtain the row range of the next pixel data segment in the input * stream. If there are no more pixel data segments, null is returned. * * @return Row range, or null. * * @exception IOException * Thrown if an I/O error occurred. */ public abstract Range getRowRange() throws IOException; /** * Obtain the column range of the next pixel data segment in the input * stream. If there are no more pixel data segments, null is returned. * * @return Column range, or null. * * @exception IOException * Thrown if an I/O error occurred. */ public abstract Range getColRange() throws IOException; /** * Read the next pixel data segment from the input stream. If there are * no more pixel data segments, the readSegment() method does * nothing. If storage is not already allocated in the underlying matrix * for the pixel rows and columns in the pixel data segment, the * readSegment() method allocates the necessary storage. * * @exception IOException * Thrown if an I/O error occurred. */ public abstract void readSegment() throws IOException; /** * Close the input stream. * * @exception IOException * Thrown if an I/O error occurred. */ public void close() throws IOException { myDis.close(); } } }

Ads help maintain this website.