JOGL v2.6.0-rc-20250706
JOGL, High-Performance Graphics Binding for Java™ (public API).
SGIImage.java
Go to the documentation of this file.
1/*
2 * Portions Copyright (c) 2005 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
6 * met:
7 *
8 * - Redistribution of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * - Redistribution in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * Neither the name of Sun Microsystems, Inc. or the names of
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * This software is provided "AS IS," without a warranty of any kind. ALL
20 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
21 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
22 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
23 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
24 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
25 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
26 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
27 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
28 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
29 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
30 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31 *
32 * You acknowledge that this software is not designed or intended for use
33 * in the design, construction, operation or maintenance of any nuclear
34 * facility.
35 *
36 * Sun gratefully acknowledges that this software was originally authored
37 * and developed by Kenneth Bradley Russell and Christopher John Kline.
38 */
39
40package com.jogamp.opengl.util.texture.spi;
41
42import java.io.*;
43
44import com.jogamp.opengl.*;
45import com.jogamp.opengl.util.texture.ImageType;
46import com.jogamp.common.util.IOUtil;
47
48/** <p> Reads and writes SGI RGB/RGBA images. </p>
49
50 <p> Written from <a href =
51 "http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/">Paul
52 Bourke's adaptation</a> of the <a href =
53 "http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/sgiversion.html">SGI
54 specification</a>. </p>
55*/
56
57public class SGIImage {
58 private final Header header;
59 private int format;
60 private byte[] data;
61 // Used for decoding RLE-compressed images
62 private int[] rowStart;
63 private int[] rowSize;
64 private int rleEnd;
65 private byte[] tmpData;
66 private byte[] tmpRead;
67
68 private static final int MAGIC = 474;
69
70 static class Header {
71 short magic; // IRIS image file magic number
72 // This should be decimal 474
73 byte storage; // Storage format
74 // 0 for uncompressed
75 // 1 for RLE compression
76 byte bpc; // Number of bytes per pixel channel
77 // Legally 1 or 2
78 short dimension; // Number of dimensions
79 // Legally 1, 2, or 3
80 // 1 means a single row, XSIZE long
81 // 2 means a single 2D image
82 // 3 means multiple 2D images
83 short xsize; // X size in pixels
84 short ysize; // Y size in pixels
85 short zsize; // Number of channels
86 // 1 indicates greyscale
87 // 3 indicates RGB
88 // 4 indicates RGB and Alpha
89 int pixmin; // Minimum pixel value
90 // This is the lowest pixel value in the image
91 int pixmax; // Maximum pixel value
92 // This is the highest pixel value in the image
93 int dummy; // Ignored
94 // Normally set to 0
95 String imagename; // Image name; 80 bytes long
96 // Must be null terminated, therefore at most 79 bytes
97 int colormap; // Colormap ID
98 // 0 - normal mode
99 // 1 - dithered, 3 mits for red and green, 2 for blue, obsolete
100 // 2 - index colour, obsolete
101 // 3 - not an image but a colourmap
102 // 404 bytes char DUMMY Ignored
103 // Should be set to 0, makes the header 512 bytes.
104
105 Header() {
106 magic = MAGIC;
107 }
108
109 Header(final DataInputStream in) throws IOException {
110 magic = in.readShort();
111 storage = in.readByte();
112 bpc = in.readByte();
113 dimension = in.readShort();
114 xsize = in.readShort();
115 ysize = in.readShort();
116 zsize = in.readShort();
117 pixmin = in.readInt();
118 pixmax = in.readInt();
119 dummy = in.readInt();
120 final byte[] tmpname = new byte[80];
121 in.read(tmpname);
122 int numChars = 0;
123 while (tmpname[numChars++] != 0);
124 imagename = new String(tmpname, 0, numChars);
125 colormap = in.readInt();
126 final byte[] tmp = new byte[404];
127 in.read(tmp);
128 }
129
130 @Override
131 public String toString() {
132 return ("magic: " + magic +
133 " storage: " + (int) storage +
134 " bpc: " + (int) bpc +
135 " dimension: " + dimension +
136 " xsize: " + xsize +
137 " ysize: " + ysize +
138 " zsize: " + zsize +
139 " pixmin: " + pixmin +
140 " pixmax: " + pixmax +
141 " imagename: " + imagename +
142 " colormap: " + colormap);
143 }
144 }
145
146 private SGIImage(final Header header) {
147 this.header = header;
148 }
149
150 /** Reads an SGI image from the specified file. */
151 public static SGIImage read(final String filename) throws IOException {
152 return read(new FileInputStream(filename));
153 }
154
155 /** Reads an SGI image from the specified InputStream. */
156 public static SGIImage read(final InputStream in) throws IOException {
157 final DataInputStream dIn = new DataInputStream(new BufferedInputStream(in));
158
159 final Header header = new Header(dIn);
160 final SGIImage res = new SGIImage(header);
161 res.decodeImage(dIn);
162 return res;
163 }
164
165 /** Writes this SGIImage to the specified file name. If
166 flipVertically is set, outputs the scanlines from top to bottom
167 rather than the default bottom to top order. */
168 public void write(final String filename, final boolean flipVertically) throws IOException {
169 write(new File(filename), flipVertically);
170 }
171
172 /** Writes this SGIImage to the specified file. If flipVertically is
173 set, outputs the scanlines from top to bottom rather than the
174 default bottom to top order. */
175 public void write(final File file, final boolean flipVertically) throws IOException {
176 writeImage(file, data, header.xsize, header.ysize, header.zsize, flipVertically);
177 }
178
179 /** Creates an SGIImage from the specified data in either RGB or
180 RGBA format. */
181 public static SGIImage createFromData(final int width,
182 final int height,
183 final boolean hasAlpha,
184 final byte[] data) {
185 final Header header = new Header();
186 header.xsize = (short) width;
187 header.ysize = (short) height;
188 header.zsize = (short) (hasAlpha ? 4 : 3);
189 final SGIImage image = new SGIImage(header);
190 image.data = data;
191 return image;
192 }
193
194 /** Returns the width of the image. */
195 public int getWidth() {
196 return header.xsize;
197 }
198
199 /** Returns the height of the image. */
200 public int getHeight() {
201 return header.ysize;
202 }
203
204 /** Returns the OpenGL format for this texture; e.g. GL.GL_RGB or GL.GL_RGBA. */
205 public int getFormat() {
206 return format;
207 }
208
209 /** Returns the raw data for this texture in the correct
210 (bottom-to-top) order for calls to glTexImage2D. */
211 public byte[] getData() { return data; }
212
213 @Override
214 public String toString() {
215 return header.toString();
216 }
217
218 //----------------------------------------------------------------------
219 // Internals only below this point
220 //
221
222 private void decodeImage(final DataInputStream in) throws IOException {
223 if (header.storage == 1) {
224 // Read RLE compression data; row starts and sizes
225 final int x = header.ysize * header.zsize;
226 rowStart = new int[x];
227 rowSize = new int[x];
228 rleEnd = 4 * 2 * x + 512;
229 for (int i = 0; i < x; i++) {
230 rowStart[i] = in.readInt();
231 }
232 for (int i = 0; i < x; i++) {
233 rowSize[i] = in.readInt();
234 }
235 tmpRead = new byte[header.xsize * 256];
236 }
237 tmpData = readAll(in);
238
239 final int xsize = header.xsize;
240 final int ysize = header.ysize;
241 final int zsize = header.zsize;
242 int lptr = 0;
243
244 data = new byte[xsize * ysize * 4];
245 final byte[] rbuf = new byte[xsize];
246 final byte[] gbuf = new byte[xsize];
247 final byte[] bbuf = new byte[xsize];
248 final byte[] abuf = new byte[xsize];
249 for (int y = 0; y < ysize; y++) {
250 if (zsize >= 4) {
251 getRow(rbuf, y, 0);
252 getRow(gbuf, y, 1);
253 getRow(bbuf, y, 2);
254 getRow(abuf, y, 3);
255 rgbatorgba(rbuf, gbuf, bbuf, abuf, data, lptr);
256 } else if (zsize == 3) {
257 getRow(rbuf, y, 0);
258 getRow(gbuf, y, 1);
259 getRow(bbuf, y, 2);
260 rgbtorgba(rbuf, gbuf, bbuf, data, lptr);
261 } else if (zsize == 2) {
262 getRow(rbuf, y, 0);
263 getRow(abuf, y, 1);
264 latorgba(rbuf, abuf, data, lptr);
265 } else {
266 getRow(rbuf, y, 0);
267 bwtorgba(rbuf, data, lptr);
268 }
269 lptr += 4 * xsize;
270 }
271 rowStart = null;
272 rowSize = null;
273 tmpData = null;
274 tmpRead = null;
275 format = GL.GL_RGBA;
276 header.zsize = 4;
277 }
278
279 private void getRow(final byte[] buf, final int y, final int z) {
280 if (header.storage == 1) {
281 final int offs = rowStart[y + z * header.ysize] - rleEnd;
282 System.arraycopy(tmpData, offs, tmpRead, 0, rowSize[y + z * header.ysize]);
283 int iPtr = 0;
284 int oPtr = 0;
285 for (;;) {
286 byte pixel = tmpRead[iPtr++];
287 int count = pixel & 0x7F;
288 if (count == 0) {
289 return;
290 }
291 if ((pixel & 0x80) != 0) {
292 while ((count--) > 0) {
293 buf[oPtr++] = tmpRead[iPtr++];
294 }
295 } else {
296 pixel = tmpRead[iPtr++];
297 while ((count--) > 0) {
298 buf[oPtr++] = pixel;
299 }
300 }
301 }
302 } else {
303 final int offs = (y * header.xsize) + (z * header.xsize * header.ysize);
304 System.arraycopy(tmpData, offs, buf, 0, header.xsize);
305 }
306 }
307
308 private void bwtorgba(final byte[] b, final byte[] dest, final int lptr) {
309 for (int i = 0; i < b.length; i++) {
310 dest[4 * i + lptr + 0] = b[i];
311 dest[4 * i + lptr + 1] = b[i];
312 dest[4 * i + lptr + 2] = b[i];
313 dest[4 * i + lptr + 3] = (byte) 0xFF;
314 }
315 }
316
317 private void latorgba(final byte[] b, final byte[] a, final byte[] dest, final int lptr) {
318 for (int i = 0; i < b.length; i++) {
319 dest[4 * i + lptr + 0] = b[i];
320 dest[4 * i + lptr + 1] = b[i];
321 dest[4 * i + lptr + 2] = b[i];
322 dest[4 * i + lptr + 3] = a[i];
323 }
324 }
325
326 private void rgbtorgba(final byte[] r, final byte[] g, final byte[] b, final byte[] dest, final int lptr) {
327 for (int i = 0; i < b.length; i++) {
328 dest[4 * i + lptr + 0] = r[i];
329 dest[4 * i + lptr + 1] = g[i];
330 dest[4 * i + lptr + 2] = b[i];
331 dest[4 * i + lptr + 3] = (byte) 0xFF;
332 }
333 }
334
335 private void rgbatorgba(final byte[] r, final byte[] g, final byte[] b, final byte[] a, final byte[] dest, final int lptr) {
336 for (int i = 0; i < b.length; i++) {
337 dest[4 * i + lptr + 0] = r[i];
338 dest[4 * i + lptr + 1] = g[i];
339 dest[4 * i + lptr + 2] = b[i];
340 dest[4 * i + lptr + 3] = a[i];
341 }
342 }
343
344 private static byte imgref(final byte[] i,
345 final int x,
346 final int y,
347 final int z,
348 final int xs,
349 final int ys,
350 final int zs) {
351 return i[(xs*ys*z)+(xs*y)+x];
352 }
353
354
355 private void writeHeader(final DataOutputStream stream,
356 final int xsize, final int ysize, final int zsize, final boolean rle) throws IOException {
357 // effects: outputs the 512-byte IRIS RGB header to STREAM, using xsize,
358 // ysize, and depth as the dimensions of the image. NOTE that
359 // the following defaults are used:
360 // STORAGE = 1 (storage format = RLE)
361 // BPC = 1 (# bytes/channel)
362 // DIMENSION = 3
363 // PIXMIN = 0
364 // PIXMAX = 255
365 // IMAGENAME = <80 nulls>
366 // COLORMAP = 0
367 // See ftp://ftp.sgi.com/pub/sgi/SGIIMAGESPEC for more details.
368
369 // write out MAGIC, STORAGE, BPC
370 stream.writeShort(474);
371 stream.write((rle ? 1 : 0));
372 stream.write(1);
373
374 // write out DIMENSION
375 stream.writeShort(3);
376
377 // write XSIZE, YSIZE, ZSIZE
378 stream.writeShort(xsize);
379 stream.writeShort(ysize);
380 stream.writeShort(zsize);
381
382 // write PIXMIN, PIXMAX
383 stream.writeInt(0);
384 stream.writeInt(255);
385
386 // write DUMMY
387 stream.writeInt(0);
388
389 // write IMAGENAME
390 for (int i = 0; i < 80; i++)
391 stream.write(0);
392
393 // write COLORMAP
394 stream.writeInt(0);
395
396 // write DUMMY (404 bytes)
397 for (int i = 0; i < 404; i++)
398 stream.write(0);
399 }
400
401 private void writeImage(final File file,
402 byte[] data,
403 final int xsize,
404 final int ysize,
405 final int zsize,
406 final boolean yflip) throws IOException {
407 // Input data is in RGBRGBRGB or RGBARGBARGBA format; first unswizzle it
408 final byte[] tmpData = new byte[xsize * ysize * zsize];
409 int dest = 0;
410 for (int i = 0; i < zsize; i++) {
411 for (int j = i; j < (xsize * ysize * zsize); j += zsize) {
412 tmpData[dest++] = data[j];
413 }
414 }
415 data = tmpData;
416
417 // requires: DATA must be an array of size XSIZE * YSIZE * ZSIZE,
418 // indexed in the following manner:
419 // data[0] ...data[xsize-1] == first row of first channel
420 // data[xsize]...data[2*xsize-1] == second row of first channel
421 // ... data[(ysize - 1) * xsize]...data[(ysize * xsize) - 1] ==
422 // last row of first channel
423 // Later channels follow the same format.
424 // *** NOTE that "first row" is defined by the BOTTOM ROW of
425 // the image. That is, the origin is in the lower left corner.
426 // effects: writes out an SGI image to FILE, RLE-compressed, INCLUDING
427 // header, of dimensions (xsize, ysize, zsize), and containing
428 // the data in DATA. If YFLIP is set, outputs the data in DATA
429 // in reverse order vertically (equivalent to a flip about the
430 // x axis).
431
432 // Build the offset tables
433 final int[] starttab = new int[ysize * zsize];
434 final int[] lengthtab = new int[ysize * zsize];
435
436 // Temporary buffer for holding RLE data.
437 // Note that this makes the assumption that RLE-compressed data will
438 // never exceed twice the size of the input data.
439 // There are surely formal proofs about how big the RLE buffer should
440 // be, as well as what the optimal look-ahead size is (i.e. don't switch
441 // copy/repeat modes for less than N repeats). However, I'm going from
442 // empirical evidence here; the break-even point seems to be a look-
443 // ahead of 3. (That is, if the three values following this one are all
444 // the same as the current value, switch to repeat mode.)
445 final int lookahead = 3;
446 final byte[] rlebuf = new byte[2 * xsize * ysize * zsize];
447
448 int cur_loc = 0; // current offset location.
449 int ptr = 0;
450 int total_size = 0;
451 int ystart = 0;
452 int yincr = 1;
453 int yend = ysize;
454
455 if (yflip) {
456 ystart = ysize - 1;
457 yend = -1;
458 yincr = -1;
459 }
460
461 final boolean DEBUG = false;
462
463 for (int z = 0; z < zsize; z++) {
464 for (int y = ystart; y != yend; y += yincr) {
465 // RLE-compress each row.
466
467 int x = 0;
468 byte count = 0;
469 boolean repeat_mode = false;
470 boolean should_switch = false;
471 final int start_ptr = ptr;
472 int num_ptr = ptr++;
473 byte repeat_val = 0;
474
475 while (x < xsize) {
476 // see if we should switch modes
477 should_switch = false;
478 if (repeat_mode) {
479 if (imgref(data, x, y, z, xsize, ysize, zsize) != repeat_val) {
480 should_switch = true;
481 }
482 } else {
483 // look ahead to see if we should switch to repeat mode.
484 // stay within the scanline for the lookahead
485 if ((x + lookahead) < xsize) {
486 should_switch = true;
487 for (int i = 1; i <= lookahead; i++) {
488 if (DEBUG)
489 System.err.println("left side was " + ((int) imgref(data, x, y, z, xsize, ysize, zsize)) +
490 ", right side was " + (int)imgref(data, x+i, y, z, xsize, ysize, zsize));
491
492 if (imgref(data, x, y, z, xsize, ysize, zsize) !=
493 imgref(data, x+i, y, z, xsize, ysize, zsize))
494 should_switch = false;
495 }
496 }
497 }
498
499 if (should_switch || (count == 127)) {
500 // update the number of elements we repeated/copied
501 if (x > 0) {
502 if (repeat_mode)
503 rlebuf[num_ptr] = count;
504 else
505 rlebuf[num_ptr] = (byte) (count | 0x80);
506 }
507 // perform mode switch if necessary; output repeat_val if
508 // switching FROM repeat mode, and set it if switching
509 // TO repeat mode.
510 if (repeat_mode) {
511 if (should_switch)
512 repeat_mode = false;
513 rlebuf[ptr++] = repeat_val;
514 } else {
515 if (should_switch)
516 repeat_mode = true;
517 repeat_val = imgref(data, x, y, z, xsize, ysize, zsize);
518 }
519
520 if (x > 0) {
521 // reset the number pointer
522 num_ptr = ptr++;
523 // reset number of bytes copied
524 count = 0;
525 }
526 }
527
528 // if not in repeat mode, copy element to ptr
529 if (!repeat_mode) {
530 rlebuf[ptr++] = imgref(data, x, y, z, xsize, ysize, zsize);
531 }
532 count++;
533
534 if (x == xsize - 1) {
535 // Need to store the number of pixels we copied/repeated.
536 if (repeat_mode) {
537 rlebuf[num_ptr] = count;
538 // If we ended the row in repeat mode, store the
539 // repeated value
540 rlebuf[ptr++] = repeat_val;
541 }
542 else
543 rlebuf[num_ptr] = (byte) (count | 0x80);
544
545 // output zero counter for the last value in the row
546 rlebuf[ptr++] = 0;
547 }
548
549 x++;
550 }
551 // output this row's length into the length table
552 final int rowlen = ptr - start_ptr;
553 if (yflip)
554 lengthtab[ysize*z+(ysize-y-1)] = rowlen;
555 else
556 lengthtab[ysize*z+y] = rowlen;
557 // add to the start table, and update the current offset
558 if (yflip)
559 starttab[ysize*z+(ysize-y-1)] = cur_loc;
560 else
561 starttab[ysize*z+y] = cur_loc;
562 cur_loc += rowlen;
563 }
564 }
565
566 // Now we have the offset tables computed, as well as the RLE data.
567 // Output this information to the file.
568 total_size = ptr;
569
570 if (DEBUG)
571 System.err.println("total_size was " + total_size);
572
573 final DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(IOUtil.getFileOutputStream(file, true)));
574
575 writeHeader(stream, xsize, ysize, zsize, true);
576
577 final int SIZEOF_INT = 4;
578 for (int i = 0; i < (ysize * zsize); i++)
579 stream.writeInt(starttab[i] + 512 + (2 * ysize * zsize * SIZEOF_INT));
580 for (int i = 0; i < (ysize * zsize); i++)
581 stream.writeInt(lengthtab[i]);
582 for (int i = 0; i < total_size; i++)
583 stream.write(rlebuf[i]);
584
585 stream.close();
586 }
587
588 private byte[] readAll(final DataInputStream in) throws IOException {
589 byte[] dest = new byte[16384];
590 int pos = 0;
591 int numRead = 0;
592
593 boolean done = false;
594
595 do {
596 numRead = in.read(dest, pos, dest.length - pos);
597 if (pos == dest.length) {
598 // Resize destination buffer
599 final byte[] newDest = new byte[2 * dest.length];
600 System.arraycopy(dest, 0, newDest, 0, pos);
601 dest = newDest;
602 }
603 if (numRead > 0) {
604 pos += numRead;
605 }
606
607 done = ((numRead == -1) || (in.available() == 0));
608 } while (!done);
609
610 // Trim destination buffer
611 if (pos != dest.length) {
612 final byte[] finalDest = new byte[pos];
613 System.arraycopy(dest, 0, finalDest, 0, pos);
614 dest = finalDest;
615 }
616
617 return dest;
618 }
619
620 // Test case
621 /*
622 import java.awt.image.*;
623 import javax.swing.*;
624
625 public static void main(String[] args) {
626 for (int i = 0; i < args.length; i++) {
627 try {
628 System.out.println(args[i] + ":");
629 SGIImage image = SGIImage.read(args[i]);
630 System.out.println(image);
631 BufferedImage img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
632 WritableRaster raster = img.getRaster();
633 DataBufferByte db = (DataBufferByte) raster.getDataBuffer();
634 byte[] src = image.getData();
635 byte[] dest = db.getData();
636 for (int j = 0; j < src.length; j += 4) {
637 dest[j + 0] = src[j + 3];
638 dest[j + 1] = src[j + 2];
639 dest[j + 2] = src[j + 1];
640 dest[j + 3] = src[j + 0];
641 }
642 // System.arraycopy(src, 0, dest, 0, src.length);
643 ImageIcon icon = new ImageIcon(img);
644 JLabel label = new JLabel();
645 label.setIcon(icon);
646 JFrame frame = new JFrame(args[i]);
647 frame.getContentPane().add(label);
648 frame.pack();
649 frame.show();
650 } catch (IOException e) {
651 e.printStackTrace();
652 }
653 }
654 }
655 */
656}
void write(final File file, final boolean flipVertically)
Writes this SGIImage to the specified file.
Definition: SGIImage.java:175
int getWidth()
Returns the width of the image.
Definition: SGIImage.java:195
static SGIImage read(final InputStream in)
Reads an SGI image from the specified InputStream.
Definition: SGIImage.java:156
byte[] getData()
Returns the raw data for this texture in the correct (bottom-to-top) order for calls to glTexImage2D.
Definition: SGIImage.java:211
static SGIImage createFromData(final int width, final int height, final boolean hasAlpha, final byte[] data)
Creates an SGIImage from the specified data in either RGB or RGBA format.
Definition: SGIImage.java:181
void write(final String filename, final boolean flipVertically)
Writes this SGIImage to the specified file name.
Definition: SGIImage.java:168
int getHeight()
Returns the height of the image.
Definition: SGIImage.java:200
int getFormat()
Returns the OpenGL format for this texture; e.g.
Definition: SGIImage.java:205
static SGIImage read(final String filename)
Reads an SGI image from the specified file.
Definition: SGIImage.java:151
static final int GL_RGBA
GL_ES_VERSION_2_0, GL_VERSION_1_1, GL_VERSION_1_0, GL_VERSION_ES_1_0 Define "GL_RGBA" with expression...
Definition: GL.java:150