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

Quick Search    Search Deep

Source code: non_com/media/jai/codec/PNMImageEncoder.java


1   /*
2    *  Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
3    *
4    *  Redistribution and use in source and binary forms, with or without
5    *  modification, are permitted provided that the following conditions are met:
6    *
7    *  -Redistributions of source code must retain the above copyright notice, this
8    *  list of conditions and the following disclaimer.
9    *
10   *  -Redistribution in binary form must reproduct 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   *
14   *  Neither the name of Sun Microsystems, Inc. or the names of contributors may
15   *  be used to endorse or promote products derived from this software without
16   *  specific prior written permission.
17   *
18   *  This software is provided "AS IS," without a warranty of any kind. ALL
19   *  EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
20   *  IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
21   *  NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
22   *  LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
23   *  OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
24   *  LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
25   *  INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
26   *  CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
27   *  OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
28   *  POSSIBILITY OF SUCH DAMAGES.
29   *
30   *  You acknowledge that Software is not designed,licensed or intended for use in
31   *  the design, construction, operation or maintenance of any nuclear facility.
32   */
33  package non_com.media.jai.codec;
34  
35  import non_com.media.jai.codec.ImageEncodeParam;
36  import non_com.media.jai.codec.ImageEncoderImpl;
37  import non_com.media.jai.codec.PNMEncodeParam;
38  import java.awt.Rectangle;
39  import java.awt.image.ColorModel;
40  import java.awt.image.ComponentSampleModel;
41  import java.awt.image.DataBuffer;
42  import java.awt.image.DataBufferByte;
43  import java.awt.image.IndexColorModel;
44  import java.awt.image.MultiPixelPackedSampleModel;
45  import java.awt.image.Raster;
46  import java.awt.image.RenderedImage;
47  import java.awt.image.SampleModel;
48  import java.io.IOException;
49  import java.io.OutputStream;
50  
51  /**
52   *  An ImageEncoder for the PNM family of file formats. <p>
53   *
54   *  The PNM file format includes PBM for monochrome images, PGM for grey scale
55   *  images, and PPM for color images. When writing the source data out, the
56   *  encoder chooses the appropriate file variant based on the actual SampleModel
57   *  of the source image. In case the source image data is unsuitable for the PNM
58   *  file format, for example when source has 4 bands or float data type, the
59   *  encoder throws an Error. <p>
60   *
61   *  The raw file format is used wherever possible, unless the PNMEncodeParam
62   *  object supplied to the constructor returns <code>true</code> from its <code>getRaw()</code>
63   *  method.
64   */
65  public class PNMImageEncoder extends ImageEncoderImpl {
66  
67    private final static int PBM_ASCII = '1';
68    private final static int PGM_ASCII = '2';
69    private final static int PPM_ASCII = '3';
70    private final static int PBM_RAW = '4';
71    private final static int PGM_RAW = '5';
72    private final static int PPM_RAW = '6';
73  
74    private final static int SPACE = ' ';
75  
76    private final static String COMMENT =
77        "# written by non_com.media.jai.codecimpl.PNMImageEncoder";
78  
79    private byte[] lineSeparator;
80  
81    private int variant;
82    private int maxValue;
83  
84  
85    /**
86     *  Constructor for the PNMImageEncoder object
87     *
88     * @param  output  Description of Parameter
89     * @param  param   Description of Parameter
90     */
91    public PNMImageEncoder(OutputStream output,
92        ImageEncodeParam param) {
93      super(output, param);
94      if (this.param == null) {
95        this.param = new PNMEncodeParam();
96      }
97    }
98  
99  
100   /**
101    *  Encodes a RenderedImage and writes the output to the OutputStream
102    *  associated with this ImageEncoder.
103    *
104    * @param  im               Description of Parameter
105    * @exception  IOException  Description of Exception
106    */
107   public void encode(RenderedImage im) throws IOException {
108     int minX = im.getMinX();
109     int minY = im.getMinY();
110     int width = im.getWidth();
111     int height = im.getHeight();
112     int tileHeight = im.getTileHeight();
113     SampleModel sampleModel = im.getSampleModel();
114     ColorModel colorModel = im.getColorModel();
115 
116     String ls = (String) java.security.AccessController.doPrivileged(
117         new sun.security.action.GetPropertyAction("line.separator"));
118     lineSeparator = ls.getBytes();
119 
120     int dataType = sampleModel.getTransferType();
121     if ((dataType == DataBuffer.TYPE_FLOAT) ||
122         (dataType == DataBuffer.TYPE_DOUBLE)) {
123       throw new RuntimeException(JaiI18N.getString("PNMImageEncoder0"));
124     }
125 
126     // Raw data can only handle bytes, everything greater must be ASCII.
127     int[] sampleSize = sampleModel.getSampleSize();
128     int numBands = sampleModel.getNumBands();
129 
130     // Colormap populated for non-bilevel IndexColorModel only.
131     byte[] reds = null;
132     byte[] greens = null;
133     byte[] blues = null;
134 
135     // Flag indicating that PB data should be inverted before writing.
136     boolean isPBMInverted = false;
137 
138     if (numBands == 1) {
139       if (colorModel instanceof IndexColorModel) {
140         IndexColorModel icm = (IndexColorModel) colorModel;
141 
142         int mapSize = icm.getMapSize();
143         if (mapSize < (1 << sampleSize[0])) {
144           throw new RuntimeException(
145               JaiI18N.getString("PNMImageEncoder1"));
146         }
147 
148         if (sampleSize[0] == 1) {
149           variant = PBM_RAW;
150 
151           // Set PBM inversion flag if 1 maps to a higher color
152           // value than 0: PBM expects white-is-zero so if this
153           // does not obtain then inversion needs to occur.
154           isPBMInverted =
155               (icm.getRed(1) + icm.getGreen(1) + icm.getBlue(1)) >
156               (icm.getRed(0) + icm.getGreen(0) + icm.getBlue(0));
157         }
158         else {
159           variant = PPM_RAW;
160 
161           reds = new byte[mapSize];
162           greens = new byte[mapSize];
163           blues = new byte[mapSize];
164 
165           icm.getReds(reds);
166           icm.getGreens(greens);
167           icm.getBlues(blues);
168         }
169       }
170       else if (sampleSize[0] == 1) {
171         variant = PBM_RAW;
172       }
173       else if (sampleSize[0] <= 8) {
174         variant = PGM_RAW;
175       }
176       else {
177         variant = PGM_ASCII;
178       }
179     }
180     else if (numBands == 3) {
181       if (sampleSize[0] <= 8 && sampleSize[1] <= 8 &&
182           sampleSize[2] <= 8) {
183         // all 3 bands must be <= 8
184         variant = PPM_RAW;
185       }
186       else {
187         variant = PPM_ASCII;
188       }
189     }
190     else {
191       throw new RuntimeException(JaiI18N.getString("PNMImageEncoder2"));
192     }
193 
194     // Read parameters
195     if (((PNMEncodeParam) param).getRaw()) {
196       if (!isRaw(variant)) {
197         boolean canUseRaw = true;
198 
199         // Make sure sampleSize for all bands no greater than 8.
200         for (int i = 0; i < sampleSize.length; i++) {
201           if (sampleSize[i] > 8) {
202             canUseRaw = false;
203             break;
204           }
205         }
206 
207         if (canUseRaw) {
208           variant += 0x3;
209         }
210       }
211     }
212     else {
213       if (isRaw(variant)) {
214         variant -= 0x3;
215       }
216     }
217 
218     maxValue = (1 << sampleSize[0]) - 1;
219 
220     // Write PNM file.
221     output.write('P');
222     // magic value
223     output.write(variant);
224 
225     output.write(lineSeparator);
226     output.write(COMMENT.getBytes());
227     // comment line
228 
229     output.write(lineSeparator);
230     writeInteger(output, width);
231     // width
232     output.write(SPACE);
233     writeInteger(output, height);
234     // height
235 
236     // Writ esample max value for non-binary images
237     if ((variant != PBM_RAW) && (variant != PBM_ASCII)) {
238       output.write(lineSeparator);
239       writeInteger(output, maxValue);
240     }
241 
242     // The spec allows a single character between the
243     // last header value and the start of the raw data.
244     if (variant == PBM_RAW ||
245         variant == PGM_RAW ||
246         variant == PPM_RAW) {
247       output.write('\n');
248     }
249 
250     // Set flag for optimal image writing case: row-packed data with
251     // correct band order if applicable.
252     boolean writeOptimal = false;
253     if (variant == PBM_RAW &&
254         sampleModel.getTransferType() == DataBuffer.TYPE_BYTE &&
255         sampleModel instanceof MultiPixelPackedSampleModel) {
256 
257       MultiPixelPackedSampleModel mppsm =
258           (MultiPixelPackedSampleModel) sampleModel;
259 
260       // Must have left-aligned bytes with unity bit stride.
261       if (mppsm.getDataBitOffset() == 0 &&
262           mppsm.getPixelBitStride() == 1) {
263 
264         writeOptimal = true;
265       }
266     }
267     else if ((variant == PGM_RAW || variant == PPM_RAW) &&
268         sampleModel instanceof ComponentSampleModel &&
269         !(colorModel instanceof IndexColorModel)) {
270 
271       ComponentSampleModel csm =
272           (ComponentSampleModel) sampleModel;
273 
274       // Pixel stride must equal band count.
275       if (csm.getPixelStride() == numBands) {
276         writeOptimal = true;
277 
278         // Band offsets must equal band indices.
279         if (variant == PPM_RAW) {
280           int[] bandOffsets = csm.getBandOffsets();
281           for (int b = 0; b < numBands; b++) {
282             if (bandOffsets[b] != b) {
283               writeOptimal = false;
284               break;
285             }
286           }
287         }
288       }
289     }
290 
291     // Write using an optimal approach if possible.
292     if (writeOptimal) {
293       int bytesPerRow = variant == PBM_RAW ?
294           (width + 7) / 8 : width * sampleModel.getNumBands();
295       int numYTiles = im.getNumYTiles();
296       Rectangle stripRect =
297           new Rectangle(im.getMinX(), im.getMinY(),
298           im.getWidth(), im.getTileHeight());
299 
300       byte[] invertedData = null;
301       if (isPBMInverted) {
302         invertedData = new byte[bytesPerRow];
303       }
304 
305       // Loop over tiles to minimize cobbling.
306       for (int j = 0; j < numYTiles; j++) {
307         // Clamp the strip to the image bounds.
308         if (j == numYTiles - 1) {
309           stripRect.height = im.getHeight() - stripRect.y;
310         }
311 
312         // Get a strip of data.
313         Raster strip = im.getData(stripRect);
314 
315         // Get the data array.
316         byte[] bdata =
317             ((DataBufferByte) strip.getDataBuffer()).getData();
318 
319         // Get the scanline stride.
320         int rowStride = variant == PBM_RAW ?
321             ((MultiPixelPackedSampleModel) sampleModel).getScanlineStride() :
322             ((ComponentSampleModel) sampleModel).getScanlineStride();
323 
324         if (rowStride == bytesPerRow && !isPBMInverted) {
325           // Write the entire strip at once.
326           output.write(bdata, 0, bdata.length);
327         }
328         else {
329           // Write the strip row-by-row.
330           int offset = 0;
331           for (int i = 0; i < tileHeight; i++) {
332             if (isPBMInverted) {
333               for (int k = 0; k < bytesPerRow; k++) {
334                 invertedData[k] =
335                     (byte) (~(bdata[offset + k] & 0xff));
336               }
337               output.write(invertedData, 0, bytesPerRow);
338             }
339             else {
340               output.write(bdata, offset, bytesPerRow);
341             }
342             offset += rowStride;
343           }
344         }
345 
346         // Increment the strip origin.
347         stripRect.y += tileHeight;
348       }
349 
350       // Write all buffered bytes and return.
351       output.flush();
352 
353       return;
354     }
355 
356     // Buffer for up to 8 rows of pixels
357     int[] pixels = new int[8 * width * numBands];
358 
359     // Also allocate a buffer to hold the data to be written to the file,
360     // so we can use array writes.
361     byte[] bpixels = reds == null ?
362         new byte[8 * width * numBands] : new byte[8 * width * 3];
363 
364     // The index of the sample being written, used to
365     // place a line separator after every 16th sample in
366     // ASCII mode.  Not used in raw mode.
367     int count = 0;
368 
369     // Process 8 rows at a time so all but the last will have
370     // a multiple of 8 pixels.  This simplifies PBM_RAW encoding.
371     int lastRow = minY + height;
372     for (int row = minY; row < lastRow; row += 8) {
373       int rows = Math.min(8, lastRow - row);
374       int size = rows * width * numBands;
375 
376       // Grab the pixels
377       Raster src = im.getData(new Rectangle(minX, row, width, rows));
378       src.getPixels(minX, row, width, rows, pixels);
379 
380       // Invert bits if necessary.
381       if (isPBMInverted) {
382         for (int k = 0; k < size; k++) {
383           pixels[k] ^= 0x00000001;
384         }
385       }
386 
387       switch (variant) {
388         case PBM_ASCII:
389         case PGM_ASCII:
390           for (int i = 0; i < size; i++) {
391             if ((count++ % 16) == 0) {
392               output.write(lineSeparator);
393             }
394             else {
395               output.write(SPACE);
396             }
397             writeInteger(output, pixels[i]);
398           }
399           output.write(lineSeparator);
400           break;
401         case PPM_ASCII:
402           if (reds == null) {
403             // no need to expand
404             for (int i = 0; i < size; i++) {
405               if ((count++ % 16) == 0) {
406                 output.write(lineSeparator);
407               }
408               else {
409                 output.write(SPACE);
410               }
411               writeInteger(output, pixels[i]);
412             }
413 
414           }
415           else {
416             for (int i = 0; i < size; i++) {
417               if ((count++ % 16) == 0) {
418                 output.write(lineSeparator);
419               }
420               else {
421                 output.write(SPACE);
422               }
423               writeInteger(output, (reds[pixels[i]] & 0xFF));
424               output.write(SPACE);
425               writeInteger(output, (greens[pixels[i]] & 0xFF));
426               output.write(SPACE);
427               writeInteger(output, (blues[pixels[i]] & 0xFF));
428             }
429           }
430           output.write(lineSeparator);
431           break;
432         case PBM_RAW:
433           // 8 pixels packed into 1 byte, the leftovers are padded.
434           int kdst = 0;
435           int ksrc = 0;
436           for (int i = 0; i < size / 8; i++) {
437             int b = (pixels[ksrc++] << 7) |
438                 (pixels[ksrc++] << 6) |
439                 (pixels[ksrc++] << 5) |
440                 (pixels[ksrc++] << 4) |
441                 (pixels[ksrc++] << 3) |
442                 (pixels[ksrc++] << 2) |
443                 (pixels[ksrc++] << 1) |
444                 pixels[ksrc++];
445             bpixels[kdst++] = (byte) b;
446           }
447 
448           // Leftover pixels, only possible at the end of the file.
449           if (size % 8 > 0) {
450             int b = 0;
451             for (int i = 0; i < size % 8; i++) {
452               b |= pixels[size + i] << (7 - i);
453             }
454             bpixels[kdst++] = (byte) b;
455           }
456           output.write(bpixels, 0, (size + 7) / 8);
457 
458           break;
459         case PGM_RAW:
460           for (int i = 0; i < size; i++) {
461             bpixels[i] = (byte) (pixels[i]);
462           }
463           output.write(bpixels, 0, size);
464           break;
465         case PPM_RAW:
466           if (reds == null) {
467             // no need to expand
468             for (int i = 0; i < size; i++) {
469               bpixels[i] = (byte) (pixels[i] & 0xFF);
470             }
471           }
472           else {
473             for (int i = 0, j = 0; i < size; i++) {
474               bpixels[j++] = reds[pixels[i]];
475               bpixels[j++] = greens[pixels[i]];
476               bpixels[j++] = blues[pixels[i]];
477             }
478           }
479           output.write(bpixels, 0, bpixels.length);
480           break;
481       }
482     }
483 
484     // Force all buffered bytes to be written out.
485     output.flush();
486   }
487 
488 
489   /**
490    *  Returns true if file variant is raw format, false if ASCII.
491    *
492    * @param  v  Description of Parameter
493    * @return    The Raw value
494    */
495   private boolean isRaw(int v) {
496     return (v >= PBM_RAW);
497   }
498 
499 
500   /**
501    *  Writes an integer to the output in ASCII format.
502    *
503    * @param  output           Description of Parameter
504    * @param  i                Description of Parameter
505    * @exception  IOException  Description of Exception
506    */
507   private void writeInteger(OutputStream output, int i) throws IOException {
508     output.write(Integer.toString(i).getBytes());
509   }
510 
511 
512   /**
513    *  Writes a byte to the output in ASCII format.
514    *
515    * @param  output           Description of Parameter
516    * @param  b                Description of Parameter
517    * @exception  IOException  Description of Exception
518    */
519   private void writeByte(OutputStream output, byte b) throws IOException {
520     output.write(Byte.toString(b).getBytes());
521   }
522 }