KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > pdfbox > pdmodel > graphics > xobject > PDCcitt


1 /**
2  * Copyright (c) 2005-2006, www.pdfbox.org
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  * 3. Neither the name of pdfbox; nor the names of its
14  * contributors may be used to endorse or promote products derived from this
15  * software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  * http://www.pdfbox.org
29  *
30  */

31 package org.pdfbox.pdmodel.graphics.xobject;
32
33 import java.awt.image.BufferedImage JavaDoc;
34 import java.io.InputStream JavaDoc;
35 import java.io.IOException JavaDoc;
36 import java.io.OutputStream JavaDoc;
37
38 import java.util.ArrayList JavaDoc;
39 import java.util.List JavaDoc;
40
41 import javax.imageio.ImageIO JavaDoc;
42
43 import org.pdfbox.cos.COSDictionary;
44 import org.pdfbox.cos.COSName;
45 import org.pdfbox.io.RandomAccess;
46
47 import org.pdfbox.pdmodel.PDDocument;
48 import org.pdfbox.pdmodel.common.PDStream;
49 import org.pdfbox.pdmodel.graphics.color.PDDeviceGray;
50
51 /**
52  * An image class for CCITT Fax.
53  *
54  * @author <a HREF="ben@benlitchfield.com">Ben Litchfield</a>
55  * @author paul king
56  * @version $Revision: 1.4 $
57  */

58 public class PDCcitt extends PDXObjectImage
59 {
60     private static final List JavaDoc FAX_FILTERS = new ArrayList JavaDoc();
61     
62     static
63     {
64         FAX_FILTERS.add( COSName.CCITTFAX_DECODE.getName() );
65         FAX_FILTERS.add( COSName.CCITTFAX_DECODE_ABBREVIATION.getName() );
66     }
67     
68     /**
69      * Standard constructor.
70      *
71      * @param ccitt The PDStream that already contains all ccitt information.
72      */

73     public PDCcitt(PDStream ccitt)
74     {
75         super(ccitt, "tiff");
76         
77     }
78     
79     /**
80      * Construct from a tiff file.
81      *
82      * @param doc The document to create the image as part of.
83      * @param raf The random access TIFF file which contains a suitable CCITT compressed image
84      * @throws IOException If there is an error reading the tiff data.
85      */

86     
87     public PDCcitt( PDDocument doc, RandomAccess raf ) throws IOException JavaDoc
88     {
89         super( new PDStream(doc),"tiff");
90         // super( new PDStream( doc, null, true ), "tiff" );
91

92         COSDictionary decodeParms = new COSDictionary();
93         
94         COSDictionary dic = getCOSStream();
95       
96         extractFromTiff(raf, getCOSStream().createFilteredStream(),decodeParms);
97         
98         dic.setItem( COSName.FILTER, COSName.CCITTFAX_DECODE);
99         dic.setItem( COSName.SUBTYPE, COSName.IMAGE);
100         dic.setItem( COSName.TYPE, COSName.getPDFName( "XObject" ) );
101         dic.setItem( "DecodeParms", decodeParms);
102         
103         setBitsPerComponent( 1 );
104         setColorSpace( new PDDeviceGray() );
105         setWidth( decodeParms.getInt("Columns") );
106         setHeight( decodeParms.getInt("Rows") );
107         
108     }
109     
110     /**
111      * Returns an image of the CCITT Fax, or null if TIFFs are not supported. (Requires additional JAI Image filters )
112      *
113      * {@inheritDoc}
114      */

115     public BufferedImage JavaDoc getRGBImage() throws IOException JavaDoc
116     {
117         // ImageIO.scanForPlugins();
118
return ImageIO.read(new TiffWrapper(getPDStream().getPartiallyFilteredStream( FAX_FILTERS ),getCOSStream()));
119     }
120     
121     /**
122      * This writes a tiff to out.
123      *
124      * {@inheritDoc}
125      */

126     public void write2OutputStream(OutputStream JavaDoc out) throws IOException JavaDoc
127     {
128         InputStream JavaDoc data = new TiffWrapper(getPDStream().getPartiallyFilteredStream( FAX_FILTERS ),getCOSStream());
129         byte[] buf = new byte[1024];
130         int amountRead = -1;
131         while( (amountRead = data.read( buf )) != -1 )
132         {
133             out.write( buf, 0, amountRead );
134         }
135     }
136     
137     /**
138      * Extract the ccitt stream from the tiff file.
139      *
140      * @param raf - TIFF File
141      * @param os - Stream to write raw ccitt data two
142      * @param parms - COSDictionary which the encoding parameters are added to
143      * @throws IOException If there is an error reading/writing to/from the stream
144      */

145     private void extractFromTiff(RandomAccess raf, OutputStream JavaDoc os, COSDictionary parms) throws IOException JavaDoc
146     {
147         try
148         {
149             
150             // First check the basic tiff header
151
raf.seek(0);
152             char endianess = (char) raf.read();
153             if ((char) raf.read() != endianess)
154             {
155                 throw new IOException JavaDoc("Not a valid tiff file");
156             }
157             //ensure that endianess is either M or I
158
if (endianess != 'M' && endianess != 'I')
159             {
160                 throw new IOException JavaDoc("Not a valid tiff file");
161             }
162             int magicNumber = readshort(endianess, raf);
163             if( magicNumber != 42)
164             {
165                 throw new IOException JavaDoc("Not a valid tiff file");
166             }
167             
168             // Relocate to the first set of tags
169
raf.seek(readlong(endianess, raf));
170             
171             int numtags = readshort(endianess, raf);
172             
173             // The number 50 is somewhat arbitary, it just stops us load up junk from somewhere and tramping on
174
if (numtags > 50)
175             {
176                 throw new IOException JavaDoc("Not a valid tiff file");
177             }
178             
179             // Loop through the tags, some will convert to items in the parms dictionary
180
// Other point us to where to find the data stream
181
// The only parm which might change as a result of other options is K, so
182
// We'll deal with that as a special;
183

184             int k=-1000; // Default Non CCITT compression
185
int dataoffset=0;
186             int datalength=0;
187             
188             for (int i=0; i < numtags; i++)
189             {
190                 int tag = readshort(endianess, raf);
191                 int type = readshort(endianess, raf);
192                 int count = readlong(endianess, raf);
193                 int val = readlong(endianess, raf); // See note
194

195                 // Note, we treated that value as a long. The value always occupies 4 bytes
196
// But it might only use the first byte or two. Depending on endianess we might need to correct
197
// Note we ignore all other types, they are of little interest for PDFs/CCITT Fax
198
if (endianess == 'M')
199                 {
200                     switch (type)
201                     {
202                         case 1:
203                         {
204                             val = val >> 24;
205                             break; // byte value
206
}
207                         case 3:
208                         {
209                             val = val >> 16;
210                             break; // short value
211
}
212                         case 4:
213                         {
214                             break; // long value
215
}
216                         default:
217                         {
218                             //do nothing
219
}
220                     }
221                 }
222                 switch (tag)
223                 {
224                     case 256:
225                     {
226                         parms.setInt("Columns",val);
227                         break;
228                     }
229                     case 257:
230                     {
231                         parms.setInt("Rows",val);
232                         break;
233                     }
234                     case 259:
235                     {
236                         if (val == 4)
237                         {
238                             k=-1;
239                         }
240                         if (val == 3)
241                         {
242                             k=0;
243                         }
244                         break; // T6/T4 Compression
245
}
246                     case 262:
247                     {
248                         if (val == 1)
249                         {
250                             parms.setBoolean("BlackIs1", true);
251                         }
252                         break;
253                     }
254                     case 273:
255                     {
256                         if (count == 1)
257                         {
258                             dataoffset=val;
259                         }
260                         break;
261                     }
262                     case 279:
263                     {
264                         if (count == 1)
265                         {
266                             datalength=val;
267                         }
268                         break;
269                     }
270                     case 292:
271                     {
272                         if (val == 1)
273                         {
274                             k=50; // T4 2D - arbitary K value
275
}
276                         break;
277                     }
278                     case 324:
279                     {
280                         if (count == 1)
281                         {
282                             dataoffset=val;
283                         }
284                         break;
285                     }
286                     case 325:
287                     {
288                         if (count == 1)
289                         {
290                             datalength=val;
291                         }
292                         break;
293                     }
294                     default:
295                     {
296                         //do nothing
297
}
298                 }
299             }
300             
301             if (k == -1000)
302             {
303                 throw new IOException JavaDoc("First image in tiff is not CCITT T4 or T6 compressed");
304             }
305             if (dataoffset == 0)
306             {
307                 throw new IOException JavaDoc("First image in tiff is not a single tile/strip");
308             }
309             
310             parms.setInt("K",k);
311             
312             raf.seek(dataoffset);
313             
314             byte[] buf = new byte[8192];
315             int amountRead = -1;
316             while( (amountRead = raf.read( buf,0, Math.min(8192,datalength) )) > 0 )
317             {
318                 datalength -= amountRead;
319                 os.write( buf, 0, amountRead );
320             }
321         
322         }
323         finally
324         {
325             os.close();
326         }
327     }
328     
329     private int readshort(char endianess, RandomAccess raf) throws IOException JavaDoc
330     {
331         if (endianess == 'I')
332         {
333             return raf.read() | (raf.read() << 8);
334         }
335         return (raf.read() << 8) | raf.read();
336     }
337     
338     private int readlong(char endianess, RandomAccess raf) throws IOException JavaDoc
339     {
340         if (endianess == 'I')
341         {
342             return raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24);
343         }
344         return (raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) | raf.read();
345     }
346
347     
348     /**
349      * Extends InputStream to wrap the data from the CCITT Fax with a suitable TIFF Header.
350      * For details see www.tiff.org, which contains useful information including pointers to the
351      * TIFF 6.0 Specification
352      *
353      */

354     private class TiffWrapper extends InputStream JavaDoc
355     {
356         
357         private int currentOffset; // When reading, where in the tiffheader are we.
358
private byte[] tiffheader; // Byte array to store tiff header data
359
private InputStream JavaDoc datastream; // Original InputStream
360

361         private TiffWrapper(InputStream JavaDoc rawstream, COSDictionary options)
362         {
363                 buildHeader(options);
364                 currentOffset=0;
365                 datastream = rawstream;
366         }
367         
368         // Implement basic methods from InputStream
369
/**
370          * {@inheritDoc}
371          */

372         public boolean markSupported()
373         {
374             return false;
375         }
376         /**
377          * {@inheritDoc}
378          */

379         public void reset() throws IOException JavaDoc
380         {
381             throw new IOException JavaDoc("reset not supported");
382         }
383         
384         /**
385          * For simple read, take a byte from the tiffheader array or pass through.
386          *
387          * {@inheritDoc}
388          */

389         public int read() throws IOException JavaDoc
390         {
391             if (currentOffset < tiffheader.length)
392             {
393                 return tiffheader[currentOffset++];
394             }
395             return datastream.read();
396         }
397         
398         /**
399          * For read methods only return as many bytes as we have left in the header
400          * if we've exhausted the header, pass through to the InputStream of the raw CCITT data.
401          *
402          * {@inheritDoc}
403          */

404         public int read(byte[] data) throws IOException JavaDoc
405         {
406             if (currentOffset < tiffheader.length)
407             {
408                 int length = java.lang.Math.min(tiffheader.length - currentOffset, data.length);
409                 if (length > 0)
410                 {
411                     System.arraycopy(tiffheader, currentOffset, data, 0, length);
412                 }
413                 currentOffset += length;
414                 return length;
415             }
416             else
417             {
418                 return datastream.read(data);
419             }
420         }
421
422         /**
423          * For read methods only return as many bytes as we have left in the header
424          * if we've exhausted the header, pass through to the InputStream of the raw CCITT data.
425          *
426          * {@inheritDoc}
427          */

428         public int read(byte[] data, int off, int len) throws IOException JavaDoc
429         {
430             if (currentOffset < tiffheader.length)
431             {
432                 int length = java.lang.Math.min(tiffheader.length - currentOffset, len);
433                 if (length > 0)
434                 {
435                     System.arraycopy(tiffheader, currentOffset, data, off, length);
436                 }
437                 currentOffset += length;
438                 return length;
439             }
440             else
441             {
442                 return datastream.read(data,off,len);
443             }
444         }
445         
446         /**
447          * When skipping if any header data not yet read, only allow to skip what we've in the buffer
448          * Otherwise just pass through.
449          *
450          * {@inheritDoc}
451          */

452         public long skip(long n) throws IOException JavaDoc
453         {
454             if (currentOffset < tiffheader.length)
455             {
456                 long length = Math.min(tiffheader.length - currentOffset, n);
457                 currentOffset += length;
458                 return length;
459             }
460             else
461             {
462                 return datastream.skip(n);
463             }
464         }
465         
466         // Static data for the beginning of the TIFF header
467
private final byte[] basicHeader = {
468                 'I','I',42,0,8,0,0,0, // File introducer and pointer to first IFD
469
0,0}; // Number of tags start with two
470

471         
472         private int additionalOffset; // Offset in header to additional data
473

474         // Builds up the tiffheader based on the options passed through.
475
private void buildHeader(COSDictionary options)
476         {
477
478             final int numOfTags = 10; // The maximum tags we'll fill
479
final int maxAdditionalData = 24; // The maximum amount of additional data
480
// outside the IFDs. (bytes)
481

482             // The length of the header will be the length of the basic header (10)
483
// plus 12 bytes for each IFD, 4 bytes as a pointer to the next IFD (will be 0)
484
// plus the length of the additional data
485

486             tiffheader = new byte[10 + (12 * numOfTags ) + 4 + maxAdditionalData];
487             java.util.Arrays.fill(tiffheader,(byte)0);
488             System.arraycopy(basicHeader,0,tiffheader,0,basicHeader.length);
489             
490             // Additional data outside the IFD starts after the IFD's and pointer to the next IFD (0)
491
additionalOffset = 10 + (12 * numOfTags ) + 4;
492
493             // Now work out the variable values from TIFF defaults,
494
// PDF Defaults and the Dictionary for this XObject
495
short cols = 1728;
496             short rows = 0;
497             short blackis1 = 0;
498             short comptype = 3; // T4 compression
499
long t4options = 0; // Will set if 1d or 2d T4
500

501             COSDictionary decodeParms = (COSDictionary) options.getDictionaryObject("DecodeParms");
502             
503             if (decodeParms != null)
504             {
505                 cols = (short) decodeParms.getInt("Columns", cols);
506                 rows = (short) decodeParms.getInt("Rows", rows);
507                 if (decodeParms.getBoolean("BlackIs1", false))
508                 {
509                     blackis1 = 1;
510                 }
511                 int k = decodeParms.getInt("K"); // Mandatory parm
512
if (k < 0)
513                 {
514                     //T6
515
comptype = 4;
516                 }
517                 if (k > 0)
518                 {
519                     //T4 2D
520
comptype = 3;
521                     t4options = 1;
522                 }
523                 // else k = 0, leave as default T4 1D compression
524
}
525             
526             // If we couldn't get the number of rows, use the main item from XObject
527
if (rows == 0)
528             {
529                 rows = (short) options.getInt("Height", rows);
530             }
531             
532             // Now put the tags into the tiffheader
533
// These musn't exceed the maximum set above, and by TIFF spec should be sorted into
534
// Numeric sequence.
535

536             addTag(256, cols); // Columns
537
addTag(257, rows); // Rows
538
addTag(259, comptype); // T6
539
addTag(262, blackis1); // Photometric Interpretation
540
addTag(273, tiffheader.length); // Offset to start of image data - updated below
541
addTag(279, options.getInt("Length")); // Length of image data
542
addTag(282, 300, 1); // X Resolution 300 (default unit Inches) This is arbitary
543
addTag(283, 300, 1); // Y Resolution 300 (default unit Inches) This is arbitary
544
if (comptype == 3)
545             {
546                 addTag(292, t4options);
547             }
548             addTag(305, "PDFBOX"); // Software generating image
549
}
550         
551         /* Tiff types 1 = byte, 2=ascii, 3=short, 4=ulong 5=rational */
552         
553         private void addTag(int tag,long value)
554         {
555         // Adds a tag of type 4 (ulong)
556
int count = ++tiffheader[8];
557             int offset = (count-1)*12 + 10;
558             tiffheader[offset]=(byte)(tag & 0xff);
559             tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
560             tiffheader[offset+2]=4; // Type Long
561
tiffheader[offset+4]=1; // One Value
562
tiffheader[offset+8]=(byte)(value & 0xff);
563             tiffheader[offset+9]=(byte)((value>>8) & 0xff);
564             tiffheader[offset+10]=(byte)((value>>16) & 0xff);
565             tiffheader[offset+11]=(byte)((value>>24) & 0xff);
566         }
567         
568         private void addTag(int tag, short value)
569         {
570             // Adds a tag of type 3 (short)
571
int count = ++tiffheader[8];
572             int offset = (count-1)*12 + 10;
573             tiffheader[offset]=(byte)(tag & 0xff);
574             tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
575             tiffheader[offset+2]=3; // Type Short
576
tiffheader[offset+4]=1; // One Value
577
tiffheader[offset+8]=(byte)(value & 0xff);
578             tiffheader[offset+9]=(byte)((value>>8) & 0xff);
579         }
580         
581         private void addTag(int tag, String JavaDoc value)
582         {
583             // Adds a tag of type 2 (ascii)
584
int count = ++tiffheader[8];
585             int offset = (count-1)*12 + 10;
586             tiffheader[offset]=(byte)(tag & 0xff);
587             tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
588             tiffheader[offset+2]=2; // Type Ascii
589
tiffheader[offset+4]=1; // One Value
590
tiffheader[offset+8]=(byte)(additionalOffset & 0xff);
591             tiffheader[offset+9]=(byte)((additionalOffset>>8) & 0xff);
592             tiffheader[offset+10]=(byte)((additionalOffset>>16) & 0xff);
593             tiffheader[offset+11]=(byte)((additionalOffset>>24) & 0xff);
594             System.arraycopy(value.getBytes(), 0, tiffheader, additionalOffset, value.length());
595             additionalOffset += value.length() + 1;
596         }
597         
598         private void addTag(int tag, long numerator, long denominator)
599         {
600             // Adds a tag of type 5 (rational)
601
int count = ++tiffheader[8];
602             int offset = (count-1)*12 + 10;
603             tiffheader[offset]=(byte)(tag & 0xff);
604             tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
605             tiffheader[offset+2]=5; // Type Rational
606
tiffheader[offset+4]=1; // One Value
607
tiffheader[offset+8]=(byte)(additionalOffset & 0xff);
608             tiffheader[offset+9]=(byte)((additionalOffset>>8) & 0xff);
609             tiffheader[offset+10]=(byte)((additionalOffset>>16) & 0xff);
610             tiffheader[offset+11]=(byte)((additionalOffset>>24) & 0xff);
611             tiffheader[additionalOffset++]=(byte) ((numerator) & 0xFF);
612             tiffheader[additionalOffset++]=(byte) ((numerator>>8) & 0xFF);
613             tiffheader[additionalOffset++]=(byte) ((numerator>>16) & 0xFF);
614             tiffheader[additionalOffset++]=(byte) ((numerator>>24) & 0xFF);
615             tiffheader[additionalOffset++]=(byte) ((denominator) & 0xFF);
616             tiffheader[additionalOffset++]=(byte) ((denominator>>8) & 0xFF);
617             tiffheader[additionalOffset++]=(byte) ((denominator>>16) & 0xFF);
618             tiffheader[additionalOffset++]=(byte) ((denominator>>24) & 0xFF);
619         }
620     }
621 }
Popular Tags