Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/flexstor/flexdbserver/services/asset/iptc/PSDParser.java


1   /*
2    * PSDParser.java
3    *
4    * Copyright $Date: 2003/08/11 02:22:34 $ FLEXSTOR.net Inc.
5    *
6    * This work is licensed for use and distribution under license terms found at
7    * http://www.flexstor.org/license.html
8    *
9    */
10  
11  package com.flexstor.flexdbserver.services.asset.iptc;
12  
13  import java.io.IOException;
14  import java.util.Hashtable;
15  
16  import com.flexstor.common.io.xfile.FlexXFile;
17  import com.flexstor.common.io.xfile.FlexXRandomAccessFile;
18  
19  /** The PSDParser is used to parse header information for an Adobe Photoshop
20   * Document (PSD File).
21   *
22   * @author bparks
23   * @version initial
24   */
25  public class PSDParser extends IPTCParser implements IIPTCParser {
26  
27     /** The file we will parse.
28      * It is a RandomAccessFile so we can skip unimportant parts of the file.
29      */
30     private FlexXRandomAccessFile fPSDFile;
31     
32     /** The "signature" of a PSD file is always equal to "8BPS"
33      * The format of a Photoshop file is described in a document titled
34      * "Photoshop File Formats.pdf" and is available on the Adobe site.
35      */
36     private String sSignature = null;
37     
38     /** The number of channels in the image, including any alpha channels.
39      * Supported range is 1 to 24.
40      */
41     private int nChannels = -1;
42     
43     /** The height of the image in pixels.  Supported range is 1 to 30,000.
44      */
45     private int nRows = -1;
46     
47     /** The width of the image in pixels.  Supported range is 1 to 30,000.
48      */
49     private int nColumns = -1;
50     
51     /** The number of bits per channel.  Supported values are 1, 8, and 16.
52      */
53     private int nBitsPerChannel = -1;
54     
55     /** The color mode of the file.  Supported values are:  Bitmap=0; Grayscale=1;
56      * Indexed=2; RGB=3; CMYK=4; Multichannel=7; Duotone=8; Lab=9.
57      */
58     private int nColorMode = -1;
59     
60     /** The length of the color data.  Only indexed color and duotone have color
61      * mode data.  For all other modes, this will be just four bytes of 0.
62      */
63     private int nColorModeDataLength = -1;
64     
65     /* the color data will not be read or stored in a variable */  
66     
67     /** nImageResourcesLength is the total length of all Image Resource Blocks.
68      */
69     private int nImageResourcesLength = -1;
70     
71     /** This is an array of bytes which consists of the entire Image Resource
72      * Section (i.e. several individual Image Resource Blocks)
73      */
74     private byte abImageResourceSection[] = null;
75     
76     /** I think the Image Resource Block data would go nicely in a Hashtable because there
77      * may be more than one Image Resource Block in a file and we would really only care
78      * about the 2-byte ID and the N-byte Resource Data (where N is always an even number)
79      */
80     private Hashtable hImageResourceBlockHash = new Hashtable();
81     
82  
83     
84     /** Creates new PSDParser from the File parameter passed in.
85      * @param fInputFile The File to create the PSDParser from.
86      * Once the PSDParser object is instantiated, the PSDParser class
87      * will be done with the fInputFile so it can be nulled immediately
88      * after instantiation.
89      */
90     public PSDParser (FlexXFile fInputFile) {
91        if ( (fInputFile.exists () == false) || (fInputFile.isFile () == false) ) {
92           fPSDFile = null;
93        } else {
94           /* create the RandomAccessFile for the given input file */
95           try {
96              fPSDFile = new FlexXRandomAccessFile ( fInputFile, "r" );
97           } catch ( IOException ioe ) {
98              BGPError ( "IO Exception attempting to create the RAF for file");
99           }
100          /* object variables will not be initialized to anything different */
101       }
102    }
103 
104 
105    /** Parse a PSD (Photoshop) file.  In order to return true, the file must have
106     * a valid PSD file from which we can retrieve IPTC-NAA info.  This may or may
107     * not parse the IPTC-NAA data as well. (Right now I'm leaning towards doing it.)
108     *
109     * @return Returns a boolean value indicating success.
110     */
111    public boolean parseFile() {
112 
113       // The superclass's no-arg constructor is called by default.
114       
115       /** nReadStatus will be used as a temporary variable to make sure
116        * a .read() was successful.  
117        */
118       int nReadStatus = -1;
119       byte abSanityBuffer[] = new byte[12];
120       
121       // Let's try to read a few bytes of our RandomAccessFile
122       // As a sanity check, we'll check the beginning of the file.
123       // The beginning of a Photoshop file should start with bytes "8BPS 01 000000"
124       try {
125          if( fPSDFile == null )
126          {
127             System.out.println( "What is the sound of a null file opening?" );
128             return false;
129          }
130          nReadStatus = fPSDFile.read ( abSanityBuffer,0,12 );
131          // If it's a -1, we had a problem read'ing and will be unable to parse.
132          if ( nReadStatus == -1 ) {
133             BGPError ( "Unable to read the first 12 bytes of the file" );
134             closeFile();
135             return false;
136          } else {
137             // Verify that the "signature" (the first four bytes) is "8BPS"
138             sSignature = new String ( abSanityBuffer, 0, 4 );
139 
140             // Note that the signature should be verified by using the typer only thus allowing
141             // more flexibility with respect to file type versions in this code.  Thus we will
142             // bypass the signature check
143             if (true)
144             //if ( sSignature.equals ("8BPS") ) {
145                 {
146 
147                //BGPDebug ( "Photoshop signature is correct" );
148                
149                // Verify the "version" (the next two bytes) are 0 and 1 as specified in
150                // the Photoshop File Formats document.
151 
152                // Once again, bypass signature check
153                if (true)
154                //if ( (abSanityBuffer[4] == 0) && (abSanityBuffer[5] == 1) ) {
155                   {
156 
157                   //BGPDebug ( "Photoshop version is correct" );
158                   
159                   // Verify that the "reserved" area (the next six bytes) are each 0
160                   // as specified in the Photoshop File Formats document.
161                   
162                   // Bypass signature check
163                   //for ( int i=6; i<12; i++ ) {
164                   //   if ( abSanityBuffer[i] != 0)  {
165                   //      BGPError ( "'Reserved' area is not valid for Photoshop file" );
166                   //      return false;
167                   //   }
168                   //}
169                   
170                   /* We know we have a valid Photoshop file.  Parse the Rest of the Header */
171                   byte abRestOfHeader[] = new byte[18];
172                   nReadStatus = fPSDFile.read(abRestOfHeader);
173                   if ( nReadStatus == -1 ) {
174                      BGPError ( "Unable to read the next 18 bytes of the file" );
175                      closeFile();
176                      return false;
177                   } else {
178                      
179                      // Compute the nChannels from bytes 0 and 1
180                      nChannels = hexBytesToInt( abRestOfHeader, 0, 2 );
181                     // BGPDebug ( "nChannels is " + nChannels );
182 
183                      // Compute the nRows from bytes 2,3,4,5
184                      nRows = hexBytesToInt( abRestOfHeader, 2, 4 );
185                      //BGPDebug ( "nRows is " + nRows );
186 
187                      // Compute the nColumns from bytes 6,7,8,9
188                      nColumns = hexBytesToInt( abRestOfHeader, 6, 4 );
189                      //BGPDebug ( "nColumns is " + nColumns );
190                      
191                       // Compute the nBitsPerChannel from bytes 10,11
192                      nBitsPerChannel = hexBytesToInt( abRestOfHeader, 10, 2 );
193                      //BGPDebug ( "nBitsPerChannel is " + nBitsPerChannel );
194 
195                      // Compute the nColorMode from bytes 12,13
196                      nColorMode = hexBytesToInt( abRestOfHeader, 12, 2 );
197                      //BGPDebug ( "nColorMode is " + nColorMode );
198 
199                      // Compute the nColorModeDataLength from bytes 14,15,16,17
200                      nColorModeDataLength = hexBytesToInt( abRestOfHeader, 14, 4 );
201                      // Only Color Modes 2 (indexed) and 8 (duotone) will have a length.
202                      //BGPDebug ( "nColorModeDataLength is " + nColorModeDataLength );
203                      
204                   }
205                   // When done with a variable, null'ing it allows for garbase collection
206                   abRestOfHeader = null;
207                   
208                   // So far in the file, we have dealt with fixed positions.
209                   // Now we will getting into data that could be at various positions.
210                   // We left off at byte 30, we need to skip over the Color Mode data
211                   fPSDFile.skipBytes( nColorModeDataLength );
212 
213                   // obtain the length of the Image resources section.
214                   // It is important to know how much Image Resource data to read.
215                   byte abImageResourceSectionLength[] = new byte[4];
216                   nReadStatus = fPSDFile.read( abImageResourceSectionLength );
217                   if ( nReadStatus == -1 ) {
218                      BGPError ( "Unable to read the Image Resource length" );
219                      closeFile();
220                      return false;
221                   } else {
222                      // Compute the nImageResourceLength from bytes 0,1,2,3
223                      nImageResourcesLength = hexBytesToInt( abImageResourceSectionLength,0,4 );
224                      //BGPDebug ( "nImageResourceLength is " + nImageResourcesLength );
225                      abImageResourceSectionLength = null;
226                   }
227                   
228                   // read in all of the Image Resources Data
229                   abImageResourceSection = new byte[nImageResourcesLength];
230                   nReadStatus = fPSDFile.read( abImageResourceSection );
231                   if ( nReadStatus == -1 ) {
232                      BGPError ( "Unable to read the Image Resources data" );
233                      closeFile();
234                      return false;
235                   } else {
236                      // If we have obtained more than zero bytes, parse the Image Resource
237                      // Data into blocks using another method.
238                      if ( nReadStatus > 0 ) {
239                         
240                         this.parseImageResourceBlocks();
241                         
242                         // finally, attempt to parse the IPTC-NAA record if there is one.
243                         byte [] abIPTCNAArecord = getIPTCNAArecord();
244                         if ( abIPTCNAArecord != null ) {
245                            // Yes, we have IPTC-NAA data
246                            parseIPTCdata(abIPTCNAArecord);
247                            
248                            // This next loop is for debugging:
249                            /*String sTempID = null;
250                            String sTempValue = null;
251                            for( Enumeration e = hDataSets.keys(); e.hasMoreElements();) {
252                               //sTempID = new String( (String) e.nextElement() );
253                               //sTempValue = new String( (String) hDataSets.get( sTempID ) );
254                               sTempID = (String) e.nextElement();
255                               sTempValue = (String) hDataSets.get( sTempID );
256                               BGPDebug( sTempID + " is [" + sTempValue + "]" );
257                            }*/
258                            
259                         } else {
260                            BGPDebug( "There is no IPTC-NAA record in this file" );
261                         }
262                      }
263                   }
264                   
265                } else {
266                   // If signature doesn't match, then we don't have a valid Photoshop file.
267                   BGPError ( "The version is not valid for a Photoshop file" );
268                   closeFile();
269                   return false;
270                }
271             } else {
272                // If the signature doesn't match, then we do not have a valid Photoshop file.
273                BGPError ( "The signature is not valid for a Photoshop file" );
274                closeFile();
275                return false;
276             }
277          }
278       } catch ( IOException ioe ) {
279          BGPError ( "IO Exception attempting to read the file" );
280          // We had a problem reading the file and will be unable to parse.
281          closeFile();
282          return false;
283       }
284 
285       closeFile();
286       return true;
287    } // end of parseFile method
288 
289    
290    /**
291    *
292    *
293    */
294    protected void closeFile()
295    {
296         if (fPSDFile == null)
297         {
298             return;
299         }
300         
301         try
302         {
303             fPSDFile.close();
304         }
305    
306         catch ( IOException ioe ) 
307         {
308             BGPError ( "Error closing file: " + ioe.toString());
309         }
310    } // closeFile
311    
312 
313    /** This method parses the entire Image Resource Section byte array into separate
314     * Image Resource Blocks and places them in the hIRBhashtable.
315     * @return Either true or false based on whether the parsing was successful.
316     */
317    private boolean parseImageResourceBlocks () {
318       int nArrayIndex = 0;
319       String sOSType = null;
320       
321       int nUniqueID = -1;
322       int nResourceBlockSize = -1;
323       byte abResourceBlockData[] = null;
324       
325       while (nArrayIndex < abImageResourceSection.length) {
326          // Verify that the "OSType" signature is correct before attempting to
327          // read the Image Resource Block.
328          sOSType = new String( abImageResourceSection, nArrayIndex, 4 );
329          // Increment the array index counter after reading bytes from the array
330          nArrayIndex += 4;
331          
332          if ( sOSType.equals ("8BIM") ) {
333             // The next two bytes are the unique identifier
334             nUniqueID = hexBytesToInt( abImageResourceSection, nArrayIndex, 2 );
335             nArrayIndex += 2;
336             //BGPDebug ( "The unique identifier is : " + nUniqueID );
337             
338             // skip the next two bytes "PString" which consist of two bytes of 0
339             nArrayIndex += 2;
340             
341             // The next four bytes are the size of this Image Resource Block
342             nResourceBlockSize = hexBytesToInt( abImageResourceSection, nArrayIndex, 4 );
343             nArrayIndex += 4;
344             
345             //BGPDebug ( "The resource block size is : " + nResourceBlockSize );
346             
347             // copy the relevant part of the whole Image Resource array into a
348             // smaller array that is just this one Image Resource Block Data.
349             abResourceBlockData = new byte[nResourceBlockSize];
350             System.arraycopy( abImageResourceSection, nArrayIndex, 
351                               abResourceBlockData, 0, nResourceBlockSize );
352 
353             // Store the nUniqueID and abResourceBlockData in the object's
354             // hImageResourceBlockHash hashtable.
355             hImageResourceBlockHash.put( new Integer(nUniqueID), abResourceBlockData );
356                
357             // According to page 8 of the Photoshop File Formats spec document,
358             // the Image Resource Block data is "padded to make size even"
359             // so check if the size is even and add 1 if it is not.  We do this
360             // after the array copy because the "padding" is not part of the data.
361             // We do this increase only to line up for the next Image Resource Block.
362             if ((nResourceBlockSize % 2) != 0) {
363                nResourceBlockSize++;
364             }
365             
366             // Increment the array index now that we have read the Image Resource Data.
367             nArrayIndex += nResourceBlockSize;
368             
369          } else {
370             // Exit this method as soon as we hit bad data.  Due to the nature of the
371             // Image Resource Block data, it would be difficult or impossible to get
372             // back on track with a proper offset if we fall off the tracks.
373             BGPError ( "The Image Resource OSType Signature is invalid: [" + sOSType + "]");
374             return false;
375          }
376          
377       } // end of while loop
378       
379       // If we have not had any problems with parsing the whole Image Resource Section
380       // into the various Image Resource Blocks, everything must be okay.
381       return true;
382       
383    } // end of parseImageResourceBlocks method
384    
385    
386    
387    /** Obtain the number of channels in the image.
388     * @return The number of channels in the image, including alpha channels.
389     * Should be in the range 1 to 24.
390     */
391    public int getNumberOfChannels () {
392       return nChannels;
393    } // end of getNumberOfChannels
394    
395    
396    /** Obtain the height of the image.
397     * @return The height of the image in pixels.
398     * Should be in the range 1 to 30,000.
399     */
400    public int getHeight () {
401       return nRows;
402    } // end of getHeight
403    
404    
405    /** Obtain the width of the image.
406     * @return The width of the image in pixels.
407     * Should be in the range 1 to 30,000.
408     */
409    public int getWidth () {
410       return nColumns;
411    } // end of getWidth
412    
413    
414    /** Obtain the number of bits per channel.
415     * Should be either 1, 8, or 16.
416     * @return The number of bits per channel.
417     */
418    public int getBitsPerChannel () {
419       return nBitsPerChannel;
420    } // end of getBitsPerChannel
421    
422    /** Obtain the color mode of the file.
423     * <PRE>Value should be one of the following:
424     *   0 is Bitmap
425     *   1 is Grayscale
426     *   2 is Indexed
427     *   3 is RGB
428     *   4 is CMYK
429     *   7 is Multichannel
430     *   8 is Duotone
431     *   9 is Lab</PRE>
432     * @return The color mode of the file.
433     */
434    public int getColorMode () {
435       return nColorMode;
436    } // end of getColorMode
437    
438    
439    /** Obtain a specific Image Resource Data Block of the file.
440     * @return An Image Resource Block as an array of bytes.
441     *        If the specified Resource ID does not exist, null will be returned.
442     * @param nResourceID The Image Resource ID for a specific Image Resource Block.
443     */
444    public byte[] getImageResource (int nResourceID) {
445       return (byte[]) hImageResourceBlockHash.get( (new Integer(nResourceID)) );
446    } // end of getImageResource
447    
448    
449    /** Obtain the IPTC-NAA Record of the file.
450     * @return The IPTC-NAA Record Image Resource Block as an array of bytes.
451     *         If it does not exist, null will be returned.
452     */
453    public byte[] getIPTCNAArecord () {
454       return getImageResource( 0x0404 );
455    } // end of getIPTCNAArecord
456 
457    
458    /** Obtain the value for the specified Dataset number.
459     * @return the Dataset number value.
460     * @param sDatasetID The IPTC Dataset ID number (A:BB)
461     */
462    public String getDatasetValue( String sDatasetID ) {
463       return (String) hDataSets.get( sDatasetID );
464    } // end of getDataset
465    
466    
467 
468 }  // end of PSDParser class