1 /*
2  * $RCSfile: ImageComponentState.java,v $
3  *
4  * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * - Redistribution of source code must retain the above copyright
11  *   notice, this list of conditions and the following disclaimer.
12  *
13  * - Redistribution in binary form must reproduce the above copyright
14  *   notice, this list of conditions and the following disclaimer in
15  *   the documentation and/or other materials provided with the
16  *   distribution.
17  *
18  * Neither the name of Sun Microsystems, Inc. or the names of
19  * contributors may be used to endorse or promote products derived
20  * from this software without specific prior written permission.
21  *
22  * This software is provided "AS IS," without a warranty of any
23  * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
24  * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
25  * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
26  * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
27  * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
28  * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
29  * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
30  * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
31  * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
32  * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
33  * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGES.
35  *
36  * You acknowledge that this software is not designed, licensed or
37  * intended for use in the design, construction, operation or
38  * maintenance of any nuclear facility.
39  *
40  * $Revision: 1.5 $
41  * $Date: 2007/02/09 17:20:35 $
42  * $State: Exp $
43  */
44 
45 package com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d;
46 
47 import java.io.DataOutput;
48 import java.io.DataInput;
49 import java.io.ByteArrayInputStream;
50 import java.io.ByteArrayOutputStream;
51 import java.io.DataInputStream;
52 import java.io.DataOutputStream;
53 import java.util.zip.GZIPInputStream;
54 import java.util.zip.GZIPOutputStream;
55 import java.io.IOException;
56 import java.awt.Point;
57 import java.awt.image.*;
58 import javax.media.j3d.ImageComponent;
59 import com.sun.j3d.utils.scenegraph.io.retained.Controller;
60 import com.sun.j3d.utils.scenegraph.io.retained.SymbolTableData;
61 import com.sun.j3d.utils.scenegraph.io.retained.SGIORuntimeException;
62 import java.awt.color.ColorSpace;
63 import java.awt.image.DataBuffer;
64 import javax.imageio.ImageIO;
65 
66 public abstract class ImageComponentState extends NodeComponentState {
67 
68     protected int format;
69     protected int height;
70     protected int width;
71     protected boolean byReference;
72     protected boolean yUp;
73 
74     private static final int DIRECT_COLOR_MODEL = 1;
75 
76     private static final int SINGLE_PIXEL_PACKED_SAMPLE_MODEL = 1;
77 
78     private static final int DATA_BUFFER_INT = 1;
79 
80     /**
81      * Do not compress the images
82      */
83     public static final byte NO_COMPRESSION = 0;
84 
85     /**
86      * Use GZIP to compress images.
87      *
88      * GZIP decompression is very slow
89      */
90     public static final byte GZIP_COMPRESSION = 1;      // GZIP is slow to decompress
91 
92     /**
93      * Use JPEG compression for images
94      *
95      * JPEG compression is currently the default. The file format
96      * supports other compression algorithms but there is currently
97      * no API to select the algorithm. This feature is on hold pending
98      * imageio in Java 1.4
99      */
100     public static final byte JPEG_COMPRESSION = 2;
101 
ImageComponentState( SymbolTableData symbol, Controller control )102     public ImageComponentState( SymbolTableData symbol, Controller control ) {
103 	super( symbol, control );
104     }
105 
106 
writeConstructorParams( DataOutput out )107     protected void writeConstructorParams( DataOutput out ) throws
108 							IOException {
109         super.writeConstructorParams( out );
110 	out.writeInt( ((ImageComponent)node).getFormat());
111 	out.writeInt( ((ImageComponent)node).getHeight());
112 	out.writeInt( ((ImageComponent)node).getWidth());
113         out.writeBoolean( ((ImageComponent)node).isByReference() );
114         out.writeBoolean( ((ImageComponent)node).isYUp() );
115     }
116 
readConstructorParams( DataInput in )117     protected void readConstructorParams( DataInput in ) throws
118 							IOException {
119         super.readConstructorParams( in );
120 	format = in.readInt();
121 	height = in.readInt();
122 	width = in.readInt();
123         byReference = in.readBoolean();
124         yUp = in.readBoolean();
125     }
126 
writeBufferedImage( DataOutput out, BufferedImage image )127     protected void writeBufferedImage( DataOutput out,
128 				       BufferedImage image ) throws IOException {
129 
130         int compressionType = control.getImageCompression();
131 
132         out.writeByte( compressionType );
133 
134         if (compressionType==NO_COMPRESSION)
135             writeBufferedImageNoCompression( out, image );
136         else if (compressionType==GZIP_COMPRESSION)
137             writeBufferedImageGzipCompression( out, image );
138         else if (compressionType==JPEG_COMPRESSION)
139             writeBufferedImageJpegCompression( out, image );
140     }
141 
writeBufferedImageNoCompression( DataOutput out, BufferedImage image )142     private void writeBufferedImageNoCompression( DataOutput out, BufferedImage image ) throws IOException {
143         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
144         DataOutputStream dataOut = new DataOutputStream( byteStream );
145         ColorModel colorModel = (ColorModel) image.getColorModel();
146 
147         if (colorModel instanceof ComponentColorModel) {
148             ComponentColorModel cm = (ComponentColorModel) colorModel;
149             int numComponents = cm.getNumComponents();
150             int type;
151             switch (numComponents) {
152                 case 3:
153                     type = BufferedImage.TYPE_INT_RGB;
154                     break;
155                 case 4:
156                     type = BufferedImage.TYPE_INT_ARGB;
157                     break;
158                 default:
159                     throw new SGIORuntimeException("Unsupported ColorModel "+colorModel.getClass().getName() );
160 
161             }
162 
163             BufferedImage tmpBuf = new BufferedImage(image.getWidth(), image.getHeight(), type);
164             WritableRaster dstRaster = tmpBuf.getRaster();
165             Raster srcRaster = image.getRaster();
166             dstRaster.setRect(srcRaster);
167            image = tmpBuf;
168         }
169 
170         writeColorModel( dataOut, image.getColorModel() );
171         writeWritableRaster( dataOut, image.getRaster() );
172         dataOut.writeBoolean( image.isAlphaPremultiplied() );
173 
174         dataOut.close();
175 
176         byte[] buffer = byteStream.toByteArray();
177         out.writeInt( buffer.length );
178         out.write( buffer );
179     }
180 
writeBufferedImageGzipCompression( DataOutput out, BufferedImage image )181     private void writeBufferedImageGzipCompression( DataOutput out, BufferedImage image ) throws IOException {
182         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
183         GZIPOutputStream gzipStream = new GZIPOutputStream( byteStream );
184         DataOutputStream dataOut = new DataOutputStream( gzipStream );
185 
186         writeColorModel( dataOut, image.getColorModel() );
187         writeWritableRaster( dataOut, image.getRaster() );
188         dataOut.writeBoolean( image.isAlphaPremultiplied() );
189 
190         dataOut.flush();
191         gzipStream.finish();
192 
193 
194         byte[] buffer = byteStream.toByteArray();
195 
196         out.writeInt( buffer.length );
197         out.write( buffer);
198         dataOut.close();
199     }
200 
writeBufferedImageJpegCompression( DataOutput out, BufferedImage image )201     private void writeBufferedImageJpegCompression( DataOutput out, BufferedImage image ) throws IOException {
202         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
203         if (!ImageIO.write(image, "jpeg", byteStream)) {
204             throw new AssertionError("No JPEG encoder available");
205         }
206 
207         byte[] buffer = byteStream.toByteArray();
208         out.writeInt( buffer.length );
209         out.write( buffer );
210     }
211 
readBufferedImage( DataInput in )212     protected BufferedImage readBufferedImage( DataInput in ) throws IOException {
213         byte compression = in.readByte();
214 
215         if (compression==NO_COMPRESSION)
216             return readBufferedImageNoCompression( in );
217         else if (compression==GZIP_COMPRESSION)
218             return readBufferedImageGzipCompression( in );
219         else if (compression==JPEG_COMPRESSION)
220             return readBufferedImageJpegCompression( in );
221 	throw new SGIORuntimeException("Unknown Image Compression");
222     }
223 
readBufferedImageNoCompression( DataInput in )224     private BufferedImage readBufferedImageNoCompression( DataInput in ) throws IOException {
225         int size = in.readInt();
226         byte[] buffer = new byte[ size ];
227         in.readFully( buffer );
228         ByteArrayInputStream byteIn = new ByteArrayInputStream( buffer );
229         DataInputStream dataIn = new DataInputStream( byteIn );
230 
231         ColorModel colorModel = readColorModel( dataIn );
232         WritableRaster raster = readWritableRaster( dataIn );
233         boolean alphaPreMult = dataIn.readBoolean();
234         dataIn.close();
235 
236         return new BufferedImage( colorModel, raster, alphaPreMult, null );
237     }
238 
readBufferedImageGzipCompression( DataInput in )239     private BufferedImage readBufferedImageGzipCompression( DataInput in ) throws IOException {
240         int size = in.readInt();
241         byte[] buffer = new byte[ size ];
242         in.readFully( buffer );
243         ByteArrayInputStream byteIn = new ByteArrayInputStream( buffer );
244         GZIPInputStream gzipIn = new GZIPInputStream( byteIn );
245         DataInputStream dataIn = new DataInputStream( gzipIn );
246 
247         ColorModel colorModel = readColorModel( dataIn );
248         WritableRaster raster = readWritableRaster( dataIn );
249         boolean alphaPremult = dataIn.readBoolean();
250         dataIn.close();
251 
252         return new BufferedImage( colorModel, raster, alphaPremult, null );
253     }
254 
readBufferedImageJpegCompression( DataInput in )255     private BufferedImage readBufferedImageJpegCompression( DataInput in ) throws IOException {
256         int size = in.readInt();
257         byte[] buffer = new byte[ size ];
258         in.readFully( buffer );
259         ByteArrayInputStream byteStream = new ByteArrayInputStream( buffer );
260         try {
261             BufferedImage img = ImageIO.read(byteStream);
262             if (img == null) {
263                 throw new AssertionError("No ImageReader available.");
264             }
265             return img;
266         } finally {
267             byteStream.close();
268         }
269     }
270 
writeColorModel( DataOutput out, ColorModel colorModel )271     private void writeColorModel( DataOutput out, ColorModel colorModel ) throws IOException {
272         if (colorModel instanceof DirectColorModel) {
273             out.writeInt( DIRECT_COLOR_MODEL );
274             writeDirectColorModel( out, (DirectColorModel)colorModel );
275         }
276         else
277             throw new SGIORuntimeException("Unsupported ColorModel "+colorModel.getClass().getName() );
278     }
279 
readColorModel( DataInput in )280     private ColorModel readColorModel( DataInput in ) throws IOException {
281         switch( in.readInt() ) {
282             case DIRECT_COLOR_MODEL:
283                 return readDirectColorModel( in );
284         }
285 
286         throw new SGIORuntimeException( "Invalid ColorModel - File corrupt" );
287     }
288 
writeDirectColorModel( DataOutput out, DirectColorModel colorModel )289     private void writeDirectColorModel( DataOutput out,
290 					DirectColorModel colorModel ) throws IOException {
291         out.writeInt( colorModel.getPixelSize() );
292         out.writeInt( colorModel.getRedMask() );
293         out.writeInt( colorModel.getGreenMask() );
294         out.writeInt( colorModel.getBlueMask() );
295         out.writeInt( colorModel.getAlphaMask() );
296     }
297 
readDirectColorModel( DataInput in )298     private DirectColorModel readDirectColorModel( DataInput in ) throws IOException {
299         return new DirectColorModel( in.readInt(),
300                                      in.readInt(),
301                                      in.readInt(),
302                                      in.readInt(),
303                                      in.readInt() );
304     }
305 
writeWritableRaster( DataOutput out, WritableRaster raster )306     private void writeWritableRaster( DataOutput out, WritableRaster raster ) throws IOException{
307         writeSampleModel( out, raster.getSampleModel() );
308         writeDataBuffer( out, raster.getDataBuffer() );
309         Point origin = new Point();
310         // TODO Get the origin of the raster - seems to be missing from the raster API
311         out.writeInt( origin.x );
312         out.writeInt( origin.y );
313     }
314 
readWritableRaster( DataInput in )315     private WritableRaster readWritableRaster( DataInput in ) throws IOException {
316         return Raster.createWritableRaster( readSampleModel( in ),
317                                    readDataBuffer( in ),
318                                    new Point( in.readInt(), in.readInt() ));
319     }
320 
writeSampleModel( DataOutput out, SampleModel model )321     private void writeSampleModel( DataOutput out, SampleModel model ) throws IOException {
322         if (model instanceof SinglePixelPackedSampleModel) {
323             out.writeInt( SINGLE_PIXEL_PACKED_SAMPLE_MODEL );
324             writeSinglePixelPackedSampleModel( out, (SinglePixelPackedSampleModel)model );
325         } else
326             throw new SGIORuntimeException("Unsupported SampleModel "+model.getClass().getName() );
327     }
328 
readSampleModel( DataInput in )329     private SampleModel readSampleModel( DataInput in ) throws IOException {
330         switch( in.readInt() ) {
331             case SINGLE_PIXEL_PACKED_SAMPLE_MODEL:
332                 return readSinglePixelPackedSampleModel( in );
333         }
334 
335         throw new SGIORuntimeException("Invalid SampleModel - file corrupt");
336     }
337 
writeSinglePixelPackedSampleModel( DataOutput out, SinglePixelPackedSampleModel model )338     private void writeSinglePixelPackedSampleModel( DataOutput out,
339         SinglePixelPackedSampleModel model ) throws IOException {
340 
341         int[] masks = model.getBitMasks();
342         out.writeInt( masks.length );
343         for(int i=0; i<masks.length; i++)
344             out.writeInt( masks[i] );
345 
346         out.writeInt( model.getDataType() );
347         out.writeInt( model.getWidth() );
348         out.writeInt( model.getHeight() );
349         out.writeInt( model.getScanlineStride() );
350 
351     }
352 
readSinglePixelPackedSampleModel( DataInput in )353     private SinglePixelPackedSampleModel readSinglePixelPackedSampleModel( DataInput in )
354 	throws IOException {
355 
356         int masks[] = new int[ in.readInt() ];
357         for(int i=0; i<masks.length; i++)
358             masks[i] = in.readInt();
359 
360         return new SinglePixelPackedSampleModel( in.readInt(),
361                                                  in.readInt(),
362                                                  in.readInt(),
363                                                  in.readInt(),
364                                                  masks );
365     }
366 
writeDataBuffer( DataOutput out, DataBuffer buffer )367     private void writeDataBuffer( DataOutput out, DataBuffer buffer ) throws IOException {
368         if (buffer instanceof DataBufferInt) {
369             out.writeInt( DATA_BUFFER_INT );
370             writeDataBufferInt( out, (DataBufferInt)buffer );
371         } else
372             throw new SGIORuntimeException("Unsupported DataBuffer "+buffer.getClass().getName() );
373     }
374 
readDataBuffer( DataInput in )375     private DataBuffer readDataBuffer( DataInput in ) throws IOException {
376         switch( in.readInt() ) {
377             case DATA_BUFFER_INT:
378                 return readDataBufferInt( in );
379         }
380 
381         throw new SGIORuntimeException("Incorrect DataBuffer - file corrupt");
382     }
383 
writeDataBufferInt( DataOutput out, DataBufferInt buffer )384     private void writeDataBufferInt( DataOutput out, DataBufferInt buffer ) throws IOException {
385         int[][] data = buffer.getBankData();
386         out.writeInt( data.length );
387         for(int i=0; i<data.length; i++) {
388             out.writeInt( data[i].length );
389             for( int j=0; j<data[i].length; j++)
390                 out.writeInt( data[i][j] );
391         }
392 
393         out.writeInt( buffer.getSize() );
394 
395         // TODO Handle DataBufferInt offsets
396 
397     }
398 
readDataBufferInt( DataInput in )399     private DataBufferInt readDataBufferInt( DataInput in ) throws IOException {
400         int[][] data = new int[in.readInt()][];
401         for(int i=0; i<data.length; i++) {
402             data[i] = new int[ in.readInt() ];
403             for( int j=0; j<data[i].length; j++)
404                 data[i][j] = in.readInt();
405         }
406 
407 
408         return new DataBufferInt( data, in.readInt() );
409     }
410 }
411