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