001/*
002 * $RCSfile: ImgReaderPGM.java,v $
003 * $Revision: 1.1 $
004 * $Date: 2005/02/11 05:02:14 $
005 * $State: Exp $
006 *
007 * Class:                   ImageWriterRawPGM
008 *
009 * Description:             Image writer for unsigned 8 bit data in
010 *                          PGM files.
011 *
012 *
013 *
014 * COPYRIGHT:
015 *
016 * This software module was originally developed by Raphaël Grosbois and
017 * Diego Santa Cruz (Swiss Federal Institute of Technology-EPFL); Joel
018 * Askelöf (Ericsson Radio Systems AB); and Bertrand Berthelot, David
019 * Bouchard, Félix Henry, Gerard Mozelle and Patrice Onno (Canon Research
020 * Centre France S.A) in the course of development of the JPEG2000
021 * standard as specified by ISO/IEC 15444 (JPEG 2000 Standard). This
022 * software module is an implementation of a part of the JPEG 2000
023 * Standard. Swiss Federal Institute of Technology-EPFL, Ericsson Radio
024 * Systems AB and Canon Research Centre France S.A (collectively JJ2000
025 * Partners) agree not to assert against ISO/IEC and users of the JPEG
026 * 2000 Standard (Users) any of their rights under the copyright, not
027 * including other intellectual property rights, for this software module
028 * with respect to the usage by ISO/IEC and Users of this software module
029 * or modifications thereof for use in hardware or software products
030 * claiming conformance to the JPEG 2000 Standard. Those intending to use
031 * this software module in hardware or software products are advised that
032 * their use may infringe existing patents. The original developers of
033 * this software module, JJ2000 Partners and ISO/IEC assume no liability
034 * for use of this software module or modifications thereof. No license
035 * or right to this software module is granted for non JPEG 2000 Standard
036 * conforming products. JJ2000 Partners have full right to use this
037 * software module for his/her own purpose, assign or donate this
038 * software module to any third party and to inhibit third parties from
039 * using this software module for non JPEG 2000 Standard conforming
040 * products. This copyright notice must be included in all copies or
041 * derivative works of this software module.
042 *
043 * Copyright (c) 1999/2000 JJ2000 Partners.
044 * */
045package jj2000.j2k.image.input;
046
047import java.io.EOFException;
048import java.io.File;
049import java.io.IOException;
050import java.io.RandomAccessFile;
051
052import jj2000.j2k.JJ2KExceptionHandler;
053import jj2000.j2k.image.DataBlk;
054import jj2000.j2k.image.DataBlkInt;
055
056/**
057 * This class implements the ImgData interface for reading 8 bit unsigned data
058 * from a binary PGM file.
059 *
060 * <p>After being read the coefficients are level shifted by subtracting
061 * 2^(nominal bit range-1)</p>
062 *
063 * <p>The TransferType (see ImgData) of this class is TYPE_INT.</p>
064 *
065 * <P>NOTE: This class is not thread safe, for reasons of internal buffering.
066 *
067 * @see jj2000.j2k.image.ImgData
068 * */
069public class ImgReaderPGM extends ImgReader {
070
071    /** DC offset value used when reading image */
072    public static int DC_OFFSET = 128;
073
074    /** Where to read the data from */
075    private RandomAccessFile in;
076    
077    /** The offset of the raw pixel data in the PGM file */
078    private int offset;
079
080    /** The number of bits that determine the nominal dynamic range */
081    private int rb;
082    
083    /** The line buffer. */
084    // This makes the class not thrad safe
085    // (but it is not the only one making it so)
086    private byte buf[];
087
088    /** Temporary DataBlkInt object (needed when encoder uses floating-point
089        filters). This avoid allocating new DataBlk at each time */
090    private DataBlkInt intBlk;
091
092    /**
093     * Creates a new PGM file reader from the specified file.
094     *
095     * @param file The input file.
096     *
097     * @exception IOException If an error occurs while opening the file.
098     * */
099    public ImgReaderPGM(File file) throws IOException {
100        this(new RandomAccessFile(file,"r"));
101    }
102
103    /**
104     * Creates a new PGM file reader from the specified file name.
105     *
106     * @param fname The input file name.
107     *
108     * @exception IOException If an error occurs while opening the file.
109     * */
110    public ImgReaderPGM(String fname) throws IOException {
111        this(new RandomAccessFile(fname,"r"));
112    }
113
114    /**
115     * Creates a new PGM file reader from the specified RandomAccessFile
116     * object. The file header is read to acquire the image size.
117     *
118     * @param in From where to read the data 
119     *
120     * @exception EOFException if an EOF is read
121     * @exception IOException if an error occurs when opening the file
122     * */
123    public ImgReaderPGM(RandomAccessFile in) throws EOFException, IOException {
124        this.in = in;
125
126        confirmFileType();
127        skipCommentAndWhiteSpace();
128        this.w = readHeaderInt();
129        skipCommentAndWhiteSpace();
130        this.h = readHeaderInt();
131        skipCommentAndWhiteSpace();
132        /*Read the highest pixel value from header (not used)*/
133        readHeaderInt(); 
134        this.nc=1;
135        this.rb=8;
136    }
137                
138
139    /**
140     * Closes the underlying RandomAccessFile from where the image data is
141     * being read. No operations are possible after a call to this method.
142     *
143     * @exception IOException If an I/O error occurs.
144     * */
145    public void close() throws IOException {
146        in.close();
147        in = null;
148    }
149
150    /**
151     * Returns the number of bits corresponding to the nominal range of the
152     * data in the specified component. This is the value rb (range bits) that
153     * was specified in the constructor, which normally is 8 for non bilevel
154     * data, and 1 for bilevel data.
155     *
156     * <P>If this number is <i>b</b> then the nominal range is between
157     * -2^(b-1) and 2^(b-1)-1, since unsigned data is level shifted to have a
158     * nominal average of 0.
159     *
160     * @param c The index of the component.
161     *
162     * @return The number of bits corresponding to the nominal range of the
163     * data. Fro floating-point data this value is not applicable and the
164     * return value is undefined.
165     * */
166    public int getNomRangeBits(int c) {
167        // Check component index
168        if (c != 0)
169            throw new IllegalArgumentException();
170
171        return rb;
172    }
173
174    
175    /**
176     * Returns the position of the fixed point in the specified component
177     * (i.e. the number of fractional bits), which is always 0 for this
178     * ImgReader.
179     *
180     * @param c The index of the component.
181     *
182     * @return The position of the fixed-point (i.e. the number of fractional
183     * bits). Always 0 for this ImgReader.
184     * */
185    public int getFixedPoint(int c) {
186        // Check component index
187        if (c != 0)
188            throw new IllegalArgumentException();
189        return 0;
190    }
191  
192    
193    /**
194     * Returns, in the blk argument, the block of image data containing the
195     * specifed rectangular area, in the specified component. The data is
196     * returned, as a reference to the internal data, if any, instead of as a
197     * copy, therefore the returned data should not be modified.
198     *
199     * <P> After being read the coefficients are level shifted by subtracting
200     * 2^(nominal bit range - 1)
201     *
202     * <P>The rectangular area to return is specified by the 'ulx', 'uly', 'w'
203     * and 'h' members of the 'blk' argument, relative to the current
204     * tile. These members are not modified by this method. The 'offset' and
205     * 'scanw' of the returned data can be arbitrary. See the 'DataBlk' class.
206     *
207     * <P>If the data array in <tt>blk</tt> is <tt>null</tt>, then a new one
208     * is created if necessary. The implementation of this interface may
209     * choose to return the same array or a new one, depending on what is more
210     * efficient. Therefore, the data array in <tt>blk</tt> prior to the
211     * method call should not be considered to contain the returned data, a
212     * new array may have been created. Instead, get the array from
213     * <tt>blk</tt> after the method has returned.
214     *
215     * <P>The returned data always has its 'progressive' attribute unset
216     * (i.e. false).
217     *
218     * <P>When an I/O exception is encountered the JJ2KExceptionHandler is
219     * used. The exception is passed to its handleException method. The action
220     * that is taken depends on the action that has been registered in
221     * JJ2KExceptionHandler. See JJ2KExceptionHandler for details.
222     *
223     * @param blk Its coordinates and dimensions specify the area to
224     * return. Some fields in this object are modified to return the data.
225     *
226     * @param c The index of the component from which to get the data. Only 0
227     * is valid.
228     *
229     * @return The requested DataBlk
230     *
231     * @see #getCompData
232     *
233     * @see JJ2KExceptionHandler
234     * */
235    public final DataBlk getInternCompData(DataBlk blk, int c) {
236        int k,j,i,mi;
237        int barr[];
238
239        // Check component index
240        if (c != 0)
241            throw new IllegalArgumentException();
242
243        // Check type of block provided as an argument
244        if(blk.getDataType()!=DataBlk.TYPE_INT){
245            if(intBlk==null)
246                intBlk = new DataBlkInt(blk.ulx,blk.uly,blk.w,blk.h);
247            else{
248                intBlk.ulx = blk.ulx;
249                intBlk.uly = blk.uly;
250                intBlk.w = blk.w;
251                intBlk.h = blk.h;
252            }
253            blk = intBlk;
254        }
255        
256        // Get data array
257        barr = (int[]) blk.getData();
258        if (barr == null || barr.length < blk.w*blk.h) {
259            barr = new int[blk.w*blk.h];
260            blk.setData(barr); 
261        }
262       
263        // Check line buffer
264        if (buf == null || buf.length < blk.w) {
265            buf = new byte[blk.w];
266        }
267
268        try {
269            // Read line by line
270            mi = blk.uly + blk.h;
271            for (i = blk.uly; i < mi; i++) {
272                // Reposition in input
273                in.seek(offset+i*w+blk.ulx);
274                in.read(buf,0,blk.w);
275                for (k = (i-blk.uly)*blk.w+blk.w-1, j = blk.w-1;
276                     j >= 0; j--, k--) {
277                    barr[k] = (((int)buf[j])&0xFF)-DC_OFFSET;
278                }
279            }
280        }
281        catch (IOException e) {
282            JJ2KExceptionHandler.handleException(e);
283        }
284
285        // Turn off the progressive attribute
286        blk.progressive = false;
287        // Set buffer attributes
288        blk.offset = 0;
289        blk.scanw = blk.w;
290        return blk;
291    }
292
293    /**
294     * Returns, in the blk argument, a block of image data containing the
295     * specifed rectangular area, in the specified component. The data is
296     * returned, as a copy of the internal data, therefore the returned data
297     * can be modified "in place".
298     *
299     * <P> After being read the coefficients are level shifted by subtracting
300     * 2^(nominal bit range - 1)
301     *
302     * <P>The rectangular area to return is specified by the 'ulx', 'uly', 'w'
303     * and 'h' members of the 'blk' argument, relative to the current
304     * tile. These members are not modified by this method. The 'offset' of
305     * the returned data is 0, and the 'scanw' is the same as the block's
306     * width. See the 'DataBlk' class.
307     *
308     * <P>If the data array in 'blk' is 'null', then a new one is created. If
309     * the data array is not 'null' then it is reused, and it must be large
310     * enough to contain the block's data. Otherwise an 'ArrayStoreException'
311     * or an 'IndexOutOfBoundsException' is thrown by the Java system.
312     *
313     * <P>The returned data has its 'progressive' attribute unset
314     * (i.e. false).
315     *
316     * <P>This method just calls 'getInternCompData(blk, n)'.
317     *
318     * <P>When an I/O exception is encountered the JJ2KExceptionHandler is
319     * used. The exception is passed to its handleException method. The action
320     * that is taken depends on the action that has been registered in
321     * JJ2KExceptionHandler. See JJ2KExceptionHandler for details.
322     *
323     * @param blk Its coordinates and dimensions specify the area to
324     * return. If it contains a non-null data array, then it must have the
325     * correct dimensions. If it contains a null data array a new one is
326     * created. The fields in this object are modified to return the data.
327     *
328     * @param c The index of the component from which to get the data. Only 0
329     * is valid.
330     *
331     * @return The requested DataBlk
332     *
333     * @see #getInternCompData
334     *
335     * @see JJ2KExceptionHandler
336     * */
337    public DataBlk getCompData(DataBlk blk, int c) {
338        return getInternCompData(blk,c);
339    }
340
341    /**
342     * Returns a byte read from the RandomAccessIO. The number of read byted
343     * are counted to keep track of the offset of the pixel data in the PGM
344     * file
345     *
346     * @return One byte read from the header of the PGM file.
347     *
348     * @exception IOException If an I/O error occurs.
349     *
350     * @exception EOFException If an EOF is read 
351     * */
352     private byte countedByteRead() throws IOException, EOFException{
353        offset++;
354        return in.readByte();
355    }
356    
357    /**
358     * Checks that the RandomAccessIO begins with 'P5'
359     *
360     * @exception IOException If an I/O error occurs.
361     * @exception EOFException If an EOF is read
362     * */        
363    private void confirmFileType() throws IOException, EOFException{
364        byte[] type={80,53}; // 'P5'
365        int i;
366        byte b;
367
368        for(i=0;i<2;i++){
369            b = countedByteRead();
370            if(b!=type[i]){
371                if( i==1 && b==50 )  { //i.e 'P2'
372                    throw new 
373                        IllegalArgumentException("JJ2000 does not support"+
374                                                 " ascii-PGM files. Use "+
375                                                 " raw-PGM file instead. ");
376                } else {
377                    throw new IllegalArgumentException("Not a raw-PGM file");
378                }
379            }
380        }
381    }
382    
383    /**
384     * Skips any line in the header starting with '#' and any space, tab, line
385     * feed or carriage return.
386     *
387     * @exception IOException If an I/O error occurs.  
388     * @exception EOFException if an EOF is read
389     * */
390    private void skipCommentAndWhiteSpace() throws IOException, EOFException {
391
392        boolean done=false;
393        byte b;
394        
395        while(!done){
396            b=countedByteRead();
397            if(b==35){ // Comment start
398                while(b!=10 && b!=13){ // Comment ends in end of line
399                    b=countedByteRead();
400                }
401            }else if(!(b==9||b==10||b==13||b==32)){ // If not whitespace
402                done=true;
403            }
404        }
405        // Put last valid byte in
406        offset--;
407        in.seek(offset);
408    }
409    
410   
411    /**
412     * Returns an int read from the header of the PGM file.
413     * 
414     * @return One int read from the header of the PGM file.
415     *
416     * @exception IOException If an I/O error occurs.
417     * @exception EOFException If an EOF is read 
418     * */
419    private int readHeaderInt() throws IOException, EOFException{
420        int res=0;
421        byte b=0;
422        
423        b=countedByteRead();   
424        while(b!=32&&b!=10&&b!=9&&b!=13){ // While not whitespace
425            res=res*10+b-48; // Covert ASCII to numerical value
426            b=countedByteRead();    
427        }
428        return res;
429    }
430    
431    /**
432     * Returns true if the data read was originally signed in the specified
433     * component, false if not. This method returns always false since PGM
434     * data is always unsigned.
435     *
436     * @param c The index of the component, from 0 to N-1.
437     *
438     * @return always false, since PGM data is always unsigned.
439     * */
440    public boolean isOrigSigned(int c) {
441        // Check component index
442        if (c != 0)
443            throw new IllegalArgumentException();
444        return false;
445    }
446
447    /**
448     * Returns a string of information about the object, more than 1 line
449     * long. The information string includes information from the underlying
450     * RandomAccessIO (its toString() method is called in turn).
451     *
452     * @return A string of information about the object.  
453     * */
454    public String toString() {
455        return "ImgReaderPGM: WxH = " + w + "x" + h + ", Component = 0" +
456            "\nUnderlying RandomAccessIO:\n" + in.toString();
457    }
458}