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 }