View Javadoc
1   package org.newdawn.slick.opengl;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.nio.ByteBuffer;
6   import org.lwjgl.BufferUtils;
7   import org.newdawn.slick.util.Log;
8   
9   import javax.annotation.Nullable;
10  
11  /**
12   * The PNG imge data source that is pure java reading PNGs
13   * 
14   * @author Matthias Mann (original code)
15   */
16  public class PNGImageData implements LoadableImageData {
17      /** The width of the data loaded */
18      private int width;
19      /** The height of the data loaded */
20      private int height;
21      /** The texture height */
22      private int texHeight;
23      /** The texture width */
24      private int texWidth;
25      /** The data format of this PNG */
26      private Format format;
27      /** The scratch buffer storing the image data */
28      private ByteBuffer scratch;
29  
30      /**
31       * @see org.newdawn.slick.opengl.ImageData#getFormat()
32       */
33      public Format getFormat() {
34          return format;
35      }
36  
37      /**
38       * @see org.newdawn.slick.opengl.ImageData#getImageBufferData()
39       */
40      public ByteBuffer getImageBufferData() {
41          return scratch;
42      }
43  
44      /**
45       * @see org.newdawn.slick.opengl.ImageData#getTexHeight()
46       */
47      public int getTexHeight() {
48          return texHeight;
49      }
50  
51      /**
52       * @see org.newdawn.slick.opengl.ImageData#getTexWidth()
53       */
54      public int getTexWidth() {
55          return texWidth;
56      }
57  
58      /**
59       * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream)
60       */
61      public ByteBuffer loadImage(InputStream fis) throws IOException {
62          return loadImage(fis, false, null);
63      }
64  
65      /**
66       * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream, boolean, int[])
67       */
68      public ByteBuffer loadImage(InputStream fis, boolean flipped, @Nullable int[] transparent) throws IOException {
69          return loadImage(fis, flipped, false, transparent);
70      }
71  
72      /**
73       * @see org.newdawn.slick.opengl.LoadableImageData#loadImage(java.io.InputStream, boolean, boolean, int[])
74       */
75      public ByteBuffer loadImage(InputStream fis, boolean flipped, boolean forceAlpha, @Nullable int[] transparent) throws IOException {
76          if (transparent != null) {
77              forceAlpha = true;
78          }
79  
80          PNGDecoder decoder = new PNGDecoder(fis);
81  
82          width = decoder.getWidth();
83          height = decoder.getHeight();
84          texWidth = get2Fold(width);
85          texHeight = get2Fold(height);
86  
87          final PNGDecoder.Format decoderFormat;
88          if (forceAlpha) {
89              if (decoder.isRGB()) {
90                  decoderFormat = decoder.decideTextureFormat(PNGDecoder.Format.RGBA);
91              } else {
92                  decoderFormat = decoder.decideTextureFormat(PNGDecoder.Format.LUMINANCE_ALPHA);
93              }
94          } else {
95              decoderFormat = decoder.decideTextureFormat(PNGDecoder.Format.LUMINANCE);
96          }
97  
98          switch (decoderFormat) {
99              case RGB:
100                 format = Format.RGB;
101                 break;
102             case RGBA:
103                 format = Format.RGBA;
104                 break;
105             case BGRA:
106                 format = Format.BGRA;
107                 break;
108             case LUMINANCE:
109                 format = Format.GRAY;
110                 break;
111             case LUMINANCE_ALPHA:
112                 format = Format.GRAYALPHA;
113                 break;
114             default:
115                 throw new IOException("Unsupported Image format.");
116         }
117 
118         int perPixel = format.getColorComponents();
119 
120         // Get a pointer to the image memory
121         scratch = BufferUtils.createByteBuffer(texWidth * texHeight * perPixel);
122 
123         if (flipped) {
124             decoder.decodeFlipped(scratch, texWidth * perPixel, decoderFormat);
125         } else {
126             decoder.decode(scratch, texWidth * perPixel, decoderFormat);
127         }
128 
129         if (height < texHeight-1) {
130             int topOffset = (texHeight-1) * (texWidth*perPixel);
131             int bottomOffset = (height-1) * (texWidth*perPixel);
132             for (int x=0;x<texWidth;x++) {
133                 for (int i=0;i<perPixel;i++) {
134                     scratch.put(topOffset+x+i, scratch.get(x+i));
135                     scratch.put(bottomOffset+(texWidth*perPixel)+x+i, scratch.get(bottomOffset+x+i));
136                 }
137             }
138         }
139         if (width < texWidth-1) {
140             for (int y=0;y<texHeight;y++) {
141                 for (int i=0;i<perPixel;i++) {
142                     scratch.put(((y+1)*(texWidth*perPixel))-perPixel+i, scratch.get(y*(texWidth*perPixel)+i));
143                     scratch.put((y*(texWidth*perPixel))+(width*perPixel)+i, scratch.get((y*(texWidth*perPixel))+((width-1)*perPixel)+i));
144                 }
145             }
146         }
147 
148         scratch.position(0);
149 
150         if (transparent != null) {
151             // components will now be + 1
152             final int components = format.getColorComponents();
153 
154             if (transparent.length != components - 1) {
155                 Log.warn("The amount of color components of the transparent color does not fit the number of color components of the actual image.");
156             }
157 
158             if (transparent.length < components - 1) {
159                 Log.error("Failed to apply transparent color, not enough color values in color definition.");
160             } else {
161 
162                 final int size = texWidth * texHeight * components;
163                 boolean match;
164 
165                 for (int i = 0; i < size; i += components) {
166                     match = true;
167                     for (int c = 0; c < components - 1; c++) {
168                         if (toInt(scratch.get(i + c)) != transparent[c]) {
169                             match = false;
170                             break;
171                         }
172                     }
173                     if (match) {
174                         scratch.put(i + components - 1, (byte) 0);
175                     }
176                 }
177             }
178         }
179 
180         scratch.position(0);
181 
182         return scratch;
183     }
184 
185     /**
186      * Safe convert byte to int
187      *
188      * @param b The byte to convert
189      * @return The converted byte
190      */
191     private int toInt(byte b) {
192         if (b < 0) {
193             return 256+b;
194         }
195 
196         return b;
197     }
198 
199     /**
200      * Get the closest greater power of 2 to the fold number
201      * 
202      * @param fold The target number
203      * @return The power of 2
204      */
205     private int get2Fold(int fold) {
206         int ret = 2;
207         while (ret < fold) {
208             ret *= 2;
209         }
210         return ret;
211     }
212     
213     /**
214      * @see org.newdawn.slick.opengl.LoadableImageData#configureEdging(boolean)
215      */
216     public void configureEdging(boolean edging) {
217     }
218 
219     public int getWidth() {
220         return width;
221     }
222 
223     public int getHeight() {
224         return height;
225     }
226 }
227