//******************************************************************************
//
// 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:
*
* -
* {@linkplain PJGColorImage} -- 24-bit color image, best when the image has a
* small number of discrete colors
*
-
* {@linkplain PJGHueImage} -- 24-bit hue image, best when the image has a
* continuous range of hues
*
-
* {@linkplain PJGGrayImage} -- 8-bit grayscale image
*
* 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:
*
* -
* Segment type. Stored as a one-byte integer. Legal values:
*
0 = Header segment
*
1 = Image type segment
*
2 = Height segment
*
3 = Width segment
*
4 = Creation time segment
*
5 = Comment segment
*
6 = Run length encoded 24-bit color pixel data segment
*
7 = Huffman delta encoded 8-bit grayscale pixel data segment
*
8 = Huffman delta encoded 24-bit hue pixel data segment
* -
* Segment contents. Depends on the type of segment, as described below.
*
*
* 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:
*
* -
* One byte with the value 0x50.
*
-
* One byte with the value 0x4A.
*
-
* One byte with the value 0x47.
*
-
* PJG file format version number. Stored as a four-byte integer, most
* significant byte first. Currently the PJG file format version number is 1.
*
* 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:
*
* -
* Image type. Stored as a one-byte integer. Legal values:
*
0 = 24-bit color image
*
1 = 8-bit grayscale image
*
2 = 24-bit hue image
*
*
* 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:
*
* -
* Image height in pixels. Stored as a four-byte integer, most significant byte
* first. The image height must be > 0.
*
*
* 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:
*
* -
* Image width in pixels. Stored as a four-byte integer, most significant byte
* first. The image width must be > 0.
*
*
* 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:
*
* -
* File creation time in milliseconds since midnight 01-Jan-1970 UTC. Stored as
* an eight-byte integer, most significant byte first. (This is the return value
* of the System.currentTimeMillis() method.)
*
*
* 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:
*
* -
* Comment text, a string stored in the same format as the java.io.DataInput and
* java.io.DataOutput interfaces use. This consists of the number of bytes in
* the encoded string, stored as a two-byte integer, most significant byte
* first, followed by the encoded string itself. The string is encoded using a
* modified UTF-8 encoding.
*
*
* 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 block's upper left pixel's row index, in the range 0 .. image height - 1.
* Stored as a four-byte integer, most significant byte first.
*
-
* The block's upper left pixel's column index, in the range 0 .. image width -
* 1. Stored as a four-byte integer, most significant byte first.
*
-
* Number of rows in the block. Stored as a four-byte integer, most significant
* byte first. The number of rows must be > 0. The block's row index plus the
* number of rows must be <= the image height.
*
-
* Number of columns in the block. Stored as a four-byte integer, most
* significant byte first. The number of columns must be > 0. The block's
* column index plus the number of columns must be <= the image width.
*
* 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:
*
* -
* If n <= 127, store one byte: 0nnnnnnn.
*
-
* If 128 <= n <= 32767, store two bytes: 1nnnnnn nnnnnnn.
*
-
* Otherwise, break the run into two or more runs each with n <= 32767
* and encode each run separately.
*
* Then encode the pixel value:
*
* -
* If p is in the dictionary and x <= 127, store one byte:
* 0xxxxxxx.
*
-
* If p is in the dictionary and 128 <= x <= 16383, store
* two bytes: 10xxxxxx xxxxxxxx.
*
-
* If p is in the dictionary and 16384 <= x <= 2097151,
* store three bytes: 110xxxxx xxxxxxxx xxxxxxxx.
*
-
* Otherwise, store four bytes: 11100000 pppppppp pppppppp pppppppp.
* Also, if the dictionary has fewer than 2097152 entries, add p to the
* dictionary, associating p with the next higher unused index x.
* That is, the first p added to the dictionary is associated with index
* 0, the second p added to the dictionary is associated with index 1,
* and so on.
*
*
* 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:
*
* -
* The block's upper left pixel's row index, in the range 0 .. image height - 1.
* Stored as a four-byte integer, most significant byte first.
*
-
* The block's upper left pixel's column index, in the range 0 .. image width -
* 1. Stored as a four-byte integer, most significant byte first.
*
-
* Number of rows in the block. Stored as a four-byte integer, most significant
* byte first. The number of rows must be > 0. The block's row index plus the
* number of rows must be <= the image height.
*
-
* Number of columns in the block. Stored as a four-byte integer, most
* significant byte first. The number of columns must be > 0. The block's
* column index plus the number of columns must be <= the image width.
*
* 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:
*
* -
* If the delta is 0, encode 1 bit: 0.
*
* -
* If the delta is in the range -2 through -1 inclusive, encode 4 bits:
* 10dd, where dd is the delta as a signed two's complement
* number.
*
* -
* If the delta is in the range +1 through +2 inclusive, encode 4 bits:
* 10dd, where dd is (delta - 1) as a signed two's complement
* number.
*
* -
* If the delta is in the range -10 through -3 inclusive, encode 7 bits:
* 110dddd, where dddd is (delta + 2) as a signed two's complement
* number.
*
* -
* If the delta is in the range +3 through +10 inclusive, encode 7 bits:
* 110dddd, where dddd is (delta - 3) as a signed two's complement
* number.
*
* -
* If the delta is in the range -42 through -11 inclusive, encode 10 bits:
* 1110dddddd, where dddddd is (delta + 10) as a signed two's
* complement number.
*
* -
* If the delta is in the range +11 through +42 inclusive, encode 10 bits:
* 1110dddddd, where dddddd is (delta - 11) as a signed two's
* complement number.
*
* -
* Otherwise, encode 12 bits: 1111pppppppp, where pppppppp is the
* pixel value (not the delta).
*
* 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:
*
* -
* The block's upper left pixel's row index, in the range 0 .. image height - 1.
* Stored as a four-byte integer, most significant byte first.
*
-
* The block's upper left pixel's column index, in the range 0 .. image width -
* 1. Stored as a four-byte integer, most significant byte first.
*
-
* Number of rows in the block. Stored as a four-byte integer, most significant
* byte first. The number of rows must be > 0. The block's row index plus the
* number of rows must be <= the image height.
*
-
* Number of columns in the block. Stored as a four-byte integer, most
* significant byte first. The number of columns must be > 0. The block's
* column index plus the number of columns must be <= the image width.
*
* 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.