1 /*
2  * $RCSfile: TextureLoader.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.12 $
41  * $Date: 2007/04/03 23:48:44 $
42  * $State: Exp $
43  */
44 
45 package com.sun.j3d.utils.image;
46 
47 import javax.media.j3d.*;
48 import java.awt.Image;
49 import java.awt.Component;
50 import java.awt.Transparency;
51 import java.awt.color.ColorSpace;
52 import java.awt.geom.AffineTransform;
53 import java.awt.image.*;
54 import java.io.File;
55 import java.io.IOException;
56 import java.net.URL;
57 import java.lang.reflect.Method;
58 import javax.imageio.ImageIO;
59 
60 /**
61  * This class is used for loading a texture from an Image or BufferedImage.
62  * The Image I/O API is used to load the images.  (If the JAI IIO Tools
63  * package is available, a larger set of formats can be loaded, including
64  * TIFF, JPEG2000, and so on.)
65  *
66  * Methods are provided to retrieve the Texture object and the associated
67  * ImageComponent object or a scaled version of the ImageComponent object.
68  *
69  * Default format is RGBA. Other legal formats are: RGBA, RGBA4, RGB5_A1,
70  * RGB, RGB4, RGB5, R3_G3_B2, LUM8_ALPHA8, LUM4_ALPHA4, LUMINANCE and ALPHA
71  */
72 public class TextureLoader extends Object {
73 
74     /**
75      * Optional flag - specifies that mipmaps are generated for all levels
76      */
77     public static final int GENERATE_MIPMAP =  0x01;
78 
79     /**
80      * Optional flag - specifies that the ImageComponent2D will
81      * access the image data by reference
82      *
83      * @since Java 3D 1.2
84      */
85     public static final int BY_REFERENCE = 0x02;
86 
87     /**
88      * Optional flag - specifies that the ImageComponent2D will
89      * have a y-orientation of y up, meaning the origin of the image is the
90      * lower left
91      *
92      * @since Java 3D 1.2
93      */
94     public static final int Y_UP = 0x04;
95 
96     /**
97      * Optional flag - specifies that the ImageComponent2D is allowed
98      * to have dimensions that are not a power of two. If this flag is set,
99      * TextureLoader will not perform any scaling of images. If this flag
100      * is not set, images will be scaled to the nearest power of two. This is
101      * the default mode.
102      * <p>
103      * Note that non-power-of-two textures may not be supported by all graphics
104      * cards. Applications should check whether a particular Canvas3D supports
105      * non-power-of-two textures by calling the {@link Canvas3D#queryProperties}
106      * method, and checking whether the
107      * <code>textureNonPowerOfTwoAvailable</code> property is set to true.
108      *
109      * @since Java 3D 1.5
110      */
111     public static final int ALLOW_NON_POWER_OF_TWO = 0x08;
112 
113     /*
114      * Private declaration for BufferedImage allocation
115      */
116     private static ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
117     private static int[] nBits = {8, 8, 8, 8};
118     private static int[] bandOffset = { 0, 1, 2, 3};
119     private static ComponentColorModel colorModel = new ComponentColorModel(cs, nBits, true, false, Transparency.TRANSLUCENT, 0);
120 
121     private Texture2D tex = null;
122     private BufferedImage bufferedImage = null;
123     private ImageComponent2D imageComponent = null;
124     private int textureFormat = Texture.RGBA;
125     private int imageComponentFormat = ImageComponent.FORMAT_RGBA;
126     private int flags;
127     private boolean byRef = false;
128     private boolean yUp = false;
129     private boolean forcePowerOfTwo = true;
130 
131     /**
132      * Contructs a TextureLoader object using the specified BufferedImage
133      * and default format RGBA
134      * @param bImage The BufferedImage used for loading the texture
135      *
136      * @exception NullPointerException if bImage is null
137      */
TextureLoader(BufferedImage bImage)138     public TextureLoader(BufferedImage bImage) {
139         this(bImage, null, 0);
140     }
141 
142     /**
143      * Contructs a TextureLoader object using the specified BufferedImage
144      * and format
145      * @param bImage The BufferedImage used for loading the texture
146      * @param format The format specifies which channels to use
147      *
148      * @exception NullPointerException if bImage is null
149      */
TextureLoader(BufferedImage bImage, String format)150     public TextureLoader(BufferedImage bImage, String format) {
151         this(bImage, format, 0);
152     }
153 
154     /**
155      * Contructs a TextureLoader object using the specified BufferedImage,
156      * option flags and default format RGBA
157      * @param bImage The BufferedImage used for loading the texture
158      * @param flags The flags specify what options to use in texture loading (generate mipmap etc)
159      *
160      * @exception NullPointerException if bImage is null
161      */
TextureLoader(BufferedImage bImage, int flags)162     public TextureLoader(BufferedImage bImage, int flags) {
163         this(bImage, null, flags);
164     }
165 
166     /**
167      * Contructs a TextureLoader object using the specified BufferedImage,
168      * format and option flags
169      * @param bImage The BufferedImage used for loading the texture
170      * @param format The format specifies which channels to use
171      * @param flags The flags specify what options to use in texture loading (generate mipmap etc)
172      *
173      * @exception NullPointerException if bImage is null
174      */
TextureLoader(BufferedImage bImage, String format, int flags)175     public TextureLoader(BufferedImage bImage, String format, int flags) {
176         if (bImage == null) {
177             throw new NullPointerException();
178         }
179 
180 	parseFormat(format);
181 	this.flags = flags;
182 	bufferedImage = bImage;
183         if (format==null)
184             chooseFormat(bufferedImage);
185 
186 	if ((flags & BY_REFERENCE) != 0) {
187 	    byRef = true;
188 	}
189 	if ((flags & Y_UP) != 0) {
190 	    yUp = true;
191 	}
192 	if ((flags & ALLOW_NON_POWER_OF_TWO) != 0) {
193 	    forcePowerOfTwo = false;
194 	}
195     }
196 
197     /**
198      * Contructs a TextureLoader object using the specified Image
199      * and default format RGBA
200      * @param image The Image used for loading the texture
201      * @param observer The associated image observer
202      *
203      * @exception NullPointerException if image is null
204      * @exception ImageException if there is a problem loading the image
205      */
TextureLoader(Image image, Component observer)206     public TextureLoader(Image image, Component observer) {
207 	this(image, null, 0, observer);
208     }
209 
210     /**
211      * Contructs a TextureLoader object using the specified Image
212      * and format
213      * @param image The Image used for loading the texture
214      * @param format The format specifies which channels to use
215      * @param observer The associated image observer
216      *
217      * @exception NullPointerException if image is null
218      * @exception ImageException if there is a problem loading the image
219      */
TextureLoader(Image image, String format, Component observer)220     public TextureLoader(Image image, String format, Component observer) {
221 	this(image, format, 0, observer);
222     }
223 
224     /**
225      * Contructs a TextureLoader object using the specified Image
226      * flags and default format RGBA
227      * @param image The Image used for loading the texture
228      * @param flags The flags specify what options to use in texture loading (generate mipmap etc)
229      * @param observer The associated image observer
230      *
231      * @exception NullPointerException if image is null
232      * @exception ImageException if there is a problem loading the image
233      */
TextureLoader(Image image, int flags, Component observer)234     public TextureLoader(Image image, int flags, Component observer) {
235 	this(image, null, flags, observer);
236     }
237 
238     /**
239      * Contructs a TextureLoader object using the specified Image
240      * format and option flags
241      * @param image The Image used for loading the texture
242      * @param format The format specifies which channels to use
243      * @param flags The flags specify what options to use in texture loading (generate mipmap etc)
244      * @param observer The associated image observer
245      *
246      * @exception NullPointerException if image is null
247      * @exception ImageException if there is a problem loading the image
248      */
TextureLoader(Image image, String format, int flags, Component observer)249     public TextureLoader(Image image, String format, int flags,
250                          Component observer) {
251 
252         if (image == null) {
253             throw new NullPointerException();
254         }
255 
256 	if (observer == null) {
257             observer = new java.awt.Container();
258 	}
259 
260 	parseFormat(format);
261 	this.flags = flags;
262 	bufferedImage = createBufferedImage(image, observer);
263 
264         if (bufferedImage==null) {
265             throw new ImageException("Error loading image: " + image.toString());
266         }
267 
268         if (format==null)
269             chooseFormat(bufferedImage);
270 
271 	if ((flags & BY_REFERENCE) != 0) {
272 	    byRef = true;
273 	}
274 	if ((flags & Y_UP) != 0) {
275 	    yUp = true;
276 	}
277 	if ((flags & ALLOW_NON_POWER_OF_TWO) != 0) {
278 	    forcePowerOfTwo = false;
279 	}
280     }
281 
282     /**
283      * Contructs a TextureLoader object using the specified file
284      * and default format RGBA
285      * @param fname The file that specifies an Image to load the texture with
286      * @param observer The associated image observer
287      *
288      * @exception ImageException if there is a problem reading the image
289      */
TextureLoader(String fname, Component observer)290     public TextureLoader(String fname, Component observer) {
291         this(fname, null, 0, observer);
292     }
293 
294     /**
295      * Contructs a TextureLoader object using the specified file,
296      * and format
297      * @param fname The file that specifies an Image to load the texture with
298      * @param format The format specifies which channels to use
299      * @param observer The associated image observer
300      *
301      * @exception ImageException if there is a problem reading the image
302      */
TextureLoader(String fname, String format, Component observer)303     public TextureLoader(String fname, String format, Component observer) {
304         this(fname, format, 0, observer);
305     }
306 
307     /**
308      * Contructs a TextureLoader object using the specified file,
309      * option flags and default format RGBA
310      * @param fname The file that specifies an Image to load the texture with
311      * @param flags The flags specify what options to use in texture loading (generate mipmap etc)
312      * @param observer The associated image observer
313      *
314      * @exception ImageException if there is a problem reading the image
315      */
TextureLoader(String fname, int flags, Component observer)316     public TextureLoader(String fname, int flags, Component observer) {
317         this(fname, null, flags, observer);
318     }
319 
320     /**
321      * Contructs a TextureLoader object using the specified file,
322      * format and option flags
323      * @param fname The file that specifies an Image to load the texture with
324      * @param format The format specifies which channels to use
325      * @param flags The flags specify what options to use in texture loading (generate mipmap etc)
326      * @param observer The associated image observer
327      *
328      * @exception ImageException if there is a problem reading the image
329      */
TextureLoader(final String fname, String format, int flags, Component observer)330     public TextureLoader(final String fname, String format, int flags,
331 			 Component observer) {
332 
333         if (observer == null) {
334             observer = new java.awt.Container();
335         }
336 
337         bufferedImage = (BufferedImage)
338             java.security.AccessController.doPrivileged(
339  	        new java.security.PrivilegedAction() {
340                     public Object run() {
341                         try {
342                             return ImageIO.read(new File(fname));
343                         } catch (IOException e) {
344 			    throw new ImageException(e);
345                         }
346                     }
347                 }
348             );
349 
350         if (bufferedImage==null) {
351             throw new ImageException("Error loading image: " + fname);
352         }
353 
354         parseFormat(format);
355         this.flags = flags;
356 
357         if (format==null)
358             chooseFormat(bufferedImage);
359 
360 	if ((flags & BY_REFERENCE) != 0) {
361 	    byRef = true;
362 	}
363 	if ((flags & Y_UP) != 0) {
364 	    yUp = true;
365 	}
366 	if ((flags & ALLOW_NON_POWER_OF_TWO) != 0) {
367 	    forcePowerOfTwo = false;
368 	}
369     }
370 
371     /**
372      * Contructs a TextureLoader object using the specified URL
373      * and default format RGBA
374      * @param url The URL that specifies an Image to load the texture with
375      * @param observer The associated image observer
376      *
377      * @exception ImageException if there is a problem reading the image
378      */
TextureLoader(URL url, Component observer)379     public TextureLoader(URL url, Component observer) {
380         this(url, null, 0, observer);
381     }
382 
383     /**
384      * Contructs a TextureLoader object using the specified URL,
385      * and format
386      * @param url The URL that specifies an Image to load the texture with
387      * @param format The format specifies which channels to use
388      * @param observer The associated image observer
389      *
390      * @exception ImageException if there is a problem reading the image
391      */
TextureLoader(URL url, String format, Component observer)392     public TextureLoader(URL url, String format, Component observer) {
393         this(url, format, 0, observer);
394     }
395 
396     /**
397      * Contructs a TextureLoader object using the specified URL,
398      * option flags and default format RGBA
399      * @param url The URL that specifies an Image to load the texture with
400      * @param flags The flags specify what options to use in texture loading (generate mipmap etc)
401      * @param observer The associated image observer
402      *
403      * @exception ImageException if there is a problem reading the image
404      */
TextureLoader(URL url, int flags, Component observer)405     public TextureLoader(URL url, int flags, Component observer) {
406         this(url, null, flags, observer);
407     }
408     /**
409      * Contructs a TextureLoader object using the specified URL,
410      * format and option flags
411      * @param url The url that specifies an Image to load the texture with
412      * @param format The format specifies which channels to use
413      * @param flags The flags specify what options to use in texture loading (generate mipmap etc)
414      * @param observer The associated image observer
415      *
416      * @exception ImageException if there is a problem reading the image
417      */
TextureLoader(final URL url, String format, int flags, Component observer)418     public TextureLoader(final URL url, String format, int flags,
419                          Component observer) {
420 
421         if (observer == null) {
422             observer = new java.awt.Container();
423         }
424 
425         bufferedImage = (BufferedImage)
426             java.security.AccessController.doPrivileged(
427  	        new java.security.PrivilegedAction() {
428                     public Object run() {
429                         try {
430                             return ImageIO.read(url);
431                         } catch (IOException e) {
432 			    throw new ImageException(e);
433                         }
434                     }
435                 }
436             );
437 
438         if (bufferedImage==null) {
439             throw new ImageException("Error loading image: " + url.toString());
440         }
441 
442         parseFormat(format);
443         this.flags = flags;
444 
445         if (format==null)
446             chooseFormat(bufferedImage);
447 
448 	if ((flags & BY_REFERENCE) != 0) {
449 	    byRef = true;
450 	}
451 	if ((flags & Y_UP) != 0) {
452 	    yUp = true;
453 	}
454 	if ((flags & ALLOW_NON_POWER_OF_TWO) != 0) {
455 	    forcePowerOfTwo = false;
456 	}
457     }
458 
459 
460     /**
461      * Returns the associated ImageComponent2D object
462      *
463      * @return The associated ImageComponent2D object
464      */
getImage()465     public ImageComponent2D getImage() {
466 	if (imageComponent == null)
467             imageComponent = new ImageComponent2D(imageComponentFormat,
468 						  bufferedImage, byRef, yUp);
469         return imageComponent;
470     }
471 
472     /**
473      * Returns the scaled ImageComponent2D object
474      *
475      * @param xScale The X scaling factor
476      * @param yScale The Y scaling factor
477      *
478      * @return The scaled ImageComponent2D object
479      */
getScaledImage(float xScale, float yScale)480     public ImageComponent2D getScaledImage(float xScale, float yScale) {
481 	if (xScale == 1.0f && yScale == 1.0f)
482 	    return getImage();
483 	else
484 	    return(new ImageComponent2D(imageComponentFormat,
485 					getScaledImage(bufferedImage,
486 						       xScale, yScale),
487 					byRef, yUp));
488     }
489 
490     /**
491      * Returns the scaled ImageComponent2D object
492      *
493      * @param width The desired width
494      * @param height The desired height
495      *
496      * @return The scaled ImageComponent2D object
497      */
getScaledImage(int width, int height)498     public ImageComponent2D getScaledImage(int width, int height) {
499 
500 	if (bufferedImage.getWidth() == width &&
501 	    	bufferedImage.getHeight() == height)
502 	    return getImage();
503         else
504 	    return(new ImageComponent2D(imageComponentFormat,
505 					getScaledImage(bufferedImage,
506 						       width, height),
507 					byRef, yUp));
508     }
509 
510     /**
511      * Returns the associated Texture object.
512      *
513      * @return The associated Texture object
514      */
getTexture()515     public Texture getTexture() {
516 	ImageComponent2D[] scaledImageComponents = null;
517 	BufferedImage[] scaledBufferedImages = null;
518         if (tex == null) {
519 
520           int width;
521           int height;
522 
523           if (forcePowerOfTwo) {
524               width = getClosestPowerOf2(bufferedImage.getWidth());
525               height = getClosestPowerOf2(bufferedImage.getHeight());
526 	  } else {
527               width = bufferedImage.getWidth();
528               height = bufferedImage.getHeight();
529 	  }
530 
531 	  if ((flags & GENERATE_MIPMAP) != 0) {
532 
533 	    BufferedImage origImage = bufferedImage;
534 	    int newW = width;
535 	    int newH = height;
536 	    int level = Math.max(computeLog(width), computeLog(height)) + 1;
537 	    scaledImageComponents = new ImageComponent2D[level];
538 	    scaledBufferedImages = new BufferedImage[level];
539             tex = new Texture2D(tex.MULTI_LEVEL_MIPMAP, textureFormat,
540                 width, height);
541 
542             for (int i = 0; i < level; i++) {
543                 scaledBufferedImages[i] = getScaledImage(origImage, newW, newH);
544                 scaledImageComponents[i] =  new ImageComponent2D(
545 			imageComponentFormat, scaledBufferedImages[i],
546 			byRef, yUp);
547 
548                 tex.setImage(i, scaledImageComponents[i]);
549 		if (forcePowerOfTwo) {
550 		    if (newW > 1) newW >>= 1;
551 		    if (newH > 1) newH >>= 1;
552 		} else {
553 		    if (newW > 1) {
554 			newW = (int) Math.floor(newW / 2.0);
555 		    }
556 		    if (newH > 1) {
557 			newH = (int) Math.floor(newH / 2.0);
558 		    }
559 		}
560 	        origImage = scaledBufferedImages[i];
561             }
562 
563           } else {
564 	    scaledImageComponents = new ImageComponent2D[1];
565 	    scaledBufferedImages = new BufferedImage[1];
566 
567             // Create texture from image
568             scaledBufferedImages[0] = getScaledImage(bufferedImage,
569 			width, height);
570             scaledImageComponents[0] = new ImageComponent2D(
571 			imageComponentFormat, scaledBufferedImages[0],
572 			byRef, yUp);
573 
574             tex = new Texture2D(tex.BASE_LEVEL, textureFormat, width, height);
575 
576             tex.setImage(0, scaledImageComponents[0]);
577           }
578           tex.setMinFilter(tex.BASE_LEVEL_LINEAR);
579           tex.setMagFilter(tex.BASE_LEVEL_LINEAR);
580         }
581 
582 	return tex;
583     }
584 
585     // create a BufferedImage from an Image object
createBufferedImage(Image image, Component observer)586     private BufferedImage createBufferedImage(Image image,
587                                               Component observer) {
588 
589 	int status;
590 
591         observer.prepareImage(image, null);
592         while(true) {
593 	    status = observer.checkImage(image, null);
594             if ((status & ImageObserver.ERROR) != 0) {
595                 return null;
596             } else if ((status & ImageObserver.ALLBITS) != 0) {
597                 break;
598             }
599             try {
600                 Thread.sleep(100);
601             } catch (InterruptedException e) {}
602         }
603 
604         int width = image.getWidth(observer);
605         int height = image.getHeight(observer);
606 
607 	WritableRaster wr =
608             java.awt.image.Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
609                                            width, height,
610                                            width * 4, 4,
611                                            bandOffset, null);
612 	BufferedImage bImage = new BufferedImage(colorModel, wr, false, null);
613 
614    	java.awt.Graphics g = bImage.getGraphics();
615    	g.drawImage(image, 0, 0, observer);
616 
617 	return bImage;
618     }
619 
620      /**
621      * Choose the correct ImageComponent and Texture format for the given
622      * image
623      */
chooseFormat(BufferedImage image)624     private void chooseFormat(BufferedImage image) {
625         switch (image.getType()) {
626             case BufferedImage.TYPE_4BYTE_ABGR :
627             case BufferedImage.TYPE_INT_ARGB :
628                 imageComponentFormat = ImageComponent.FORMAT_RGBA;
629                 textureFormat = Texture.RGBA;
630                 break;
631             case BufferedImage.TYPE_3BYTE_BGR :
632             case BufferedImage.TYPE_INT_BGR:
633             case BufferedImage.TYPE_INT_RGB:
634                 imageComponentFormat = ImageComponent.FORMAT_RGB;
635                 textureFormat = Texture.RGB;
636                 break;
637             case BufferedImage.TYPE_CUSTOM:
638                 if (is4ByteRGBAOr3ByteRGB(image)) {
639                     SampleModel sm = image.getSampleModel();
640                     if (sm.getNumBands() == 3) {
641                         //System.out.println("ChooseFormat Custom:TYPE_4BYTE_ABGR");
642                         imageComponentFormat = ImageComponent.FORMAT_RGB;
643                         textureFormat = Texture.RGB;
644                     }
645                     else {
646                         imageComponentFormat = ImageComponent.FORMAT_RGBA;
647                         //System.out.println("ChooseFormat Custom:TYPE_3BYTE_BGR");
648                         textureFormat = Texture.RGBA;
649                     }
650                 }
651                 break;
652             default :
653                 // System.err.println("Unoptimized Image Type "+image.getType());
654                 imageComponentFormat = ImageComponent.FORMAT_RGBA;
655                 textureFormat = Texture.RGBA;
656                 break;
657         }
658     }
659 
is4ByteRGBAOr3ByteRGB(RenderedImage ri)660     private boolean is4ByteRGBAOr3ByteRGB(RenderedImage ri) {
661         boolean value = false;
662         int i;
663         int biType = getImageType(ri);
664         if (biType != BufferedImage.TYPE_CUSTOM)
665             return false;
666         ColorModel cm = ri.getColorModel();
667         ColorSpace cs = cm.getColorSpace();
668         SampleModel sm = ri.getSampleModel();
669         boolean isAlphaPre = cm.isAlphaPremultiplied();
670         int csType = cs.getType();
671         if ( csType == ColorSpace.TYPE_RGB) {
672             int numBands = sm.getNumBands();
673             if (sm.getDataType() == DataBuffer.TYPE_BYTE) {
674                 if (cm instanceof ComponentColorModel &&
675                     sm instanceof PixelInterleavedSampleModel) {
676                     PixelInterleavedSampleModel csm =
677 				(PixelInterleavedSampleModel) sm;
678                     int[] offs = csm.getBandOffsets();
679                     ComponentColorModel ccm = (ComponentColorModel)cm;
680                     int[] nBits = ccm.getComponentSize();
681                     boolean is8Bit = true;
682                     for (i=0; i < numBands; i++) {
683                         if (nBits[i] != 8) {
684                             is8Bit = false;
685                             break;
686                         }
687                     }
688                     if (is8Bit &&
689                         offs[0] == 0 &&
690                         offs[1] == 1 &&
691                         offs[2] == 2) {
692                         if (numBands == 3) {
693                             value = true;
694                         }
695                         else if (offs[3] == 3 && !isAlphaPre) {
696                             value = true;
697                         }
698                     }
699                 }
700             }
701         }
702         return value;
703     }
704 
getImageType(RenderedImage ri)705      private int getImageType(RenderedImage ri) {
706         int imageType = BufferedImage.TYPE_CUSTOM;
707         int i;
708 
709         if (ri instanceof BufferedImage) {
710             return ((BufferedImage)ri).getType();
711         }
712         ColorModel cm = ri.getColorModel();
713         ColorSpace cs = cm.getColorSpace();
714         SampleModel sm = ri.getSampleModel();
715         int csType = cs.getType();
716         boolean isAlphaPre = cm.isAlphaPremultiplied();
717         if ( csType != ColorSpace.TYPE_RGB) {
718             if (csType == ColorSpace.TYPE_GRAY &&
719                 cm instanceof ComponentColorModel) {
720                 if (sm.getDataType() == DataBuffer.TYPE_BYTE) {
721                     imageType = BufferedImage.TYPE_BYTE_GRAY;
722                 } else if (sm.getDataType() == DataBuffer.TYPE_USHORT) {
723                     imageType = BufferedImage.TYPE_USHORT_GRAY;
724                 }
725             }
726         }
727         // RGB , only interested in BYTE ABGR and BGR for now
728         // all others will be copied to a buffered image
729         else {
730             int numBands = sm.getNumBands();
731             if (sm.getDataType() == DataBuffer.TYPE_BYTE) {
732                 if (cm instanceof ComponentColorModel &&
733                     sm instanceof PixelInterleavedSampleModel) {
734                     PixelInterleavedSampleModel csm =
735 				(PixelInterleavedSampleModel) sm;
736                     int[] offs = csm.getBandOffsets();
737                     ComponentColorModel ccm = (ComponentColorModel)cm;
738                     int[] nBits = ccm.getComponentSize();
739                     boolean is8Bit = true;
740                     for (i=0; i < numBands; i++) {
741                         if (nBits[i] != 8) {
742                             is8Bit = false;
743                             break;
744                         }
745                     }
746                     if (is8Bit &&
747                         offs[0] == numBands-1 &&
748                         offs[1] == numBands-2 &&
749                         offs[2] == numBands-3) {
750                         if (numBands == 3) {
751                             imageType = BufferedImage.TYPE_3BYTE_BGR;
752                         }
753                         else if (offs[3] == 0) {
754                             imageType = (isAlphaPre
755                                          ? BufferedImage.TYPE_4BYTE_ABGR_PRE
756                                          : BufferedImage.TYPE_4BYTE_ABGR);
757                         }
758                     }
759                 }
760             }
761         }
762         return imageType;
763     }
764 
765     // initialize appropriate format for ImageComponent and Texture
parseFormat(String format)766     private void parseFormat(String format) {
767          if (format==null)
768             return;
769 
770         if (format.equals("RGBA")) {
771             imageComponentFormat = ImageComponent.FORMAT_RGBA;
772             textureFormat = Texture.RGBA;
773 
774         } else if (format.equals("RGBA4")) {
775             imageComponentFormat = ImageComponent.FORMAT_RGBA4;
776             textureFormat = Texture.RGBA;
777 
778         } else if (format.equals("RGB5_A1")) {
779             imageComponentFormat = ImageComponent.FORMAT_RGB5_A1;
780             textureFormat = Texture.RGBA;
781 
782         } else if (format.equals("RGB")) {
783             imageComponentFormat = ImageComponent.FORMAT_RGB;
784             textureFormat = Texture.RGB;
785 
786         } else if (format.equals("RGB4")) {
787             imageComponentFormat = ImageComponent.FORMAT_RGB4;
788             textureFormat = Texture.RGB;
789 
790         } else if (format.equals("RGB5")) {
791             imageComponentFormat = ImageComponent.FORMAT_RGB5;
792             textureFormat = Texture.RGB;
793 
794         } else if (format.equals("R3_G3_B2")) {
795             imageComponentFormat = ImageComponent.FORMAT_R3_G3_B2;
796             textureFormat = Texture.RGB;
797 
798         } else if (format.equals("LUM8_ALPHA8")) {
799             imageComponentFormat = ImageComponent.FORMAT_LUM8_ALPHA8;
800             textureFormat = Texture.LUMINANCE_ALPHA;
801 
802         } else if (format.equals("LUM4_ALPHA4")) {
803             imageComponentFormat = ImageComponent.FORMAT_LUM4_ALPHA4;
804             textureFormat = Texture.LUMINANCE_ALPHA;
805 
806         } else if (format.equals("LUMINANCE")) {
807             imageComponentFormat = ImageComponent.FORMAT_CHANNEL8;
808             textureFormat = Texture.LUMINANCE;
809 
810         } else if (format.equals("ALPHA")) {
811             imageComponentFormat = ImageComponent.FORMAT_CHANNEL8;
812             textureFormat = Texture.ALPHA;
813         }
814     }
815 
816     // return a scaled image of given width and height
getScaledImage(BufferedImage origImage, int width, int height)817     private BufferedImage getScaledImage(BufferedImage origImage,
818                                          int width, int height) {
819 
820         int origW = origImage.getWidth();
821         int origH = origImage.getHeight();
822         float xScale = (float)width/(float)origW;
823         float yScale = (float)height/(float)origH;
824 
825 	return (getScaledImage(origImage, xScale, yScale));
826     }
827 
828     // return a scaled image of given x and y scale
getScaledImage(BufferedImage origImage, float xScale, float yScale)829     private BufferedImage getScaledImage(BufferedImage origImage,
830                                          float xScale, float yScale) {
831 
832 
833         // System.err.println("(1) origImage " + origImage);
834         // If the image is already the requested size, no need to scale
835         if (xScale == 1.0f && yScale == 1.0f)
836             return origImage;
837         else {
838             int scaleW = (int)(origImage.getWidth() * xScale + 0.5);
839             int scaleH = (int)(origImage.getHeight() * yScale + 0.5);
840 
841             int origImageType = origImage.getType();
842             BufferedImage scaledImage;
843             WritableRaster wr;
844 
845             if (origImageType != BufferedImage.TYPE_CUSTOM) {
846                 WritableRaster origWr = origImage.getRaster();
847                 wr = origWr.createCompatibleWritableRaster(0, 0, scaleW, scaleH);
848                 scaledImage = new BufferedImage(scaleW, scaleH, origImageType);
849             } else {
850                 int numComponents = origImage.getSampleModel().getNumBands();
851                 int[] bandOffset = new int[numComponents];
852                 int[] nBits = new int[numComponents];
853                 for (int ii=0; ii < numComponents; ii++) {
854                     bandOffset[ii] = ii;
855                     nBits[ii] = 8;
856                 }
857 
858                 wr = java.awt.image.Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
859                         scaleW, scaleH,
860                         scaleW * numComponents, numComponents,
861                         bandOffset, null);
862 
863                 int imageType;
864 
865                 switch (numComponents) {
866                     case 1:
867                         imageType = BufferedImage.TYPE_BYTE_GRAY;
868                         break;
869                     case 3:
870                         imageType = BufferedImage.TYPE_3BYTE_BGR;
871                         break;
872                     case 4:
873                         imageType = BufferedImage.TYPE_4BYTE_ABGR;
874                         break;
875                     default:
876                         throw new ImageException("Illegal number of bands : " + numComponents);
877 
878                 }
879 
880                 scaledImage = new BufferedImage(scaleW, scaleH, imageType);
881             }
882 
883             scaledImage.setData(wr);
884             java.awt.Graphics2D g2 = scaledImage.createGraphics();
885             AffineTransform at = AffineTransform.getScaleInstance(xScale,
886                     yScale);
887             g2.transform(at);
888             g2.drawImage(origImage, 0, 0, null);
889 
890             return scaledImage;
891         }
892     }
893 
computeLog(int value)894     private int computeLog(int value) {
895         int i = 0;
896 
897         if (value == 0) return -1;
898         for (;;) {
899             if (value == 1)
900                 return i;
901             value >>= 1;
902 	    i++;
903         }
904     }
905 
getClosestPowerOf2(int value)906     private int getClosestPowerOf2(int value) {
907 
908 	if (value < 1)
909 	    return value;
910 
911 	int powerValue = 1;
912 	for (;;) {
913 	    powerValue *= 2;
914 	    if (value < powerValue) {
915 		// Found max bound of power, determine which is closest
916 		int minBound = powerValue/2;
917 		if ((powerValue - value) >
918 		    (value - minBound))
919 		    return minBound;
920 		else
921 		    return powerValue;
922 	    }
923 	}
924     }
925 }
926