1ddceac0870a87f7e1b14464a3f2fe1ef3898f97
[glpng.git] / src / glpng.c
blob1ddceac0870a87f7e1b14464a3f2fe1ef3898f97
1 /*
2 * PNG loader library for OpenGL v1.45 (10/07/00)
3 * by Ben Wyatt ben@wyatt100.freeserve.co.uk
4 * Using LibPNG 1.0.2 and ZLib 1.1.3
6 * This software is provided 'as-is', without any express or implied warranty.
7 * In no event will the author be held liable for any damages arising from the
8 * use of this software.
10 * Permission is hereby granted to use, copy, modify, and distribute this
11 * source code, or portions hereof, for any purpose, without fee, subject to
12 * the following restrictions:
14 * 1. The origin of this source code must not be misrepresented. You must not
15 * claim that you wrote the original software. If you use this software in
16 * a product, an acknowledgment in the product documentation would be
17 * appreciated but is not required.
18 * 2. Altered versions must be plainly marked as such and must not be
19 * misrepresented as being the original source.
20 * 3. This notice must not be removed or altered from any source distribution.
23 #ifdef _WIN32 /* Stupid Windows needs to include windows.h before gl.h */
24 #undef FAR
25 #include <windows.h>
26 #endif
28 #include <GL/glpng.h>
29 #include <GL/gl.h>
30 #include <stdlib.h>
31 #include <math.h>
32 #include "png/png.h"
34 /* Used to decide if GL/gl.h supports the paletted extension */
35 #ifdef GL_COLOR_INDEX1_EXT
36 #define SUPPORTS_PALETTE_EXT
37 #endif
39 static unsigned char DefaultAlphaCallback(unsigned char red, unsigned char green, unsigned char blue) {
40 return 255;
43 static unsigned char StencilRed = 0, StencilGreen = 0, StencilBlue = 0;
44 static unsigned char (*AlphaCallback)(unsigned char red, unsigned char green, unsigned char blue) = DefaultAlphaCallback;
45 static int StandardOrientation = 0;
47 #ifdef SUPPORTS_PALETTE_EXT
48 #ifdef _WIN32
49 static PFNGLCOLORTABLEEXTPROC glColorTableEXT = NULL;
50 #endif
51 #endif
53 static int PalettedTextures = -1;
54 static GLint MaxTextureSize = 0;
56 /* screenGamma = displayGamma/viewingGamma
57 * displayGamma = CRT has gamma of ~2.2
58 * viewingGamma depends on platform. PC is 1.0, Mac is 1.45, SGI defaults
59 * to 1.7, but this can be checked and changed w/ /usr/sbin/gamma command.
60 * If the environment variable VIEWING_GAMMA is set, adjust gamma per this value.
62 #ifdef _MAC
63 static double screenGamma = 2.2 / 1.45;
64 #elif SGI
65 static double screenGamma = 2.2 / 1.7;
66 #else /* PC/default */
67 static double screenGamma = 2.2 / 1.0;
68 #endif
70 static char gammaExplicit = 0; /*if */
72 static void checkForGammaEnv()
74 double viewingGamma;
75 char *gammaEnv = getenv("VIEWING_GAMMA");
77 if(gammaEnv && !gammaExplicit)
79 sscanf(gammaEnv, "%lf", &viewingGamma);
80 screenGamma = 2.2/viewingGamma;
84 /* Returns a safe texture size to use (ie a power of 2), based on the current texture size "i" */
85 static int SafeSize(int i) {
86 int p;
88 if (i > MaxTextureSize) return MaxTextureSize;
90 for (p = 0; p < 24; p++)
91 if (i <= (1<<p))
92 return 1<<p;
94 return MaxTextureSize;
97 /* Resize the texture since gluScaleImage doesn't work on everything */
98 static void Resize(int components, const png_bytep d1, int w1, int h1, png_bytep d2, int w2, int h2) {
99 const float sx = (float) w1/w2, sy = (float) h1/h2;
100 int x, y, xx, yy, c;
101 png_bytep d;
103 for (y = 0; y < h2; y++) {
104 yy = (int) (y*sy)*w1;
106 for (x = 0; x < w2; x++) {
107 xx = (int) (x*sx);
108 d = d1 + (yy+xx)*components;
110 for (c = 0; c < components; c++)
111 *d2++ = *d++;
116 static int ExtSupported(const char *x) {
117 static const GLubyte *ext = NULL;
118 const char *c;
119 int xlen = strlen(x);
121 if (ext == NULL) ext = glGetString(GL_EXTENSIONS);
123 c = (const char*)ext;
125 while (*c != '\0') {
126 if (strcmp(c, x) == 0 && (c[xlen] == '\0' || c[xlen] == ' ')) return 1;
127 c++;
130 return 0;
133 #define GET(o) ((int)*(data + (o)))
135 static int HalfSize(GLint components, GLint width, GLint height, const unsigned char *data, unsigned char *d, int filter) {
136 int x, y, c;
137 int line = width*components;
139 if (width > 1 && height > 1) {
140 if (filter)
141 for (y = 0; y < height; y += 2) {
142 for (x = 0; x < width; x += 2) {
143 for (c = 0; c < components; c++) {
144 *d++ = (GET(0)+GET(components)+GET(line)+GET(line+components)) / 4;
145 data++;
147 data += components;
149 data += line;
151 else
152 for (y = 0; y < height; y += 2) {
153 for (x = 0; x < width; x += 2) {
154 for (c = 0; c < components; c++) {
155 *d++ = GET(0);
156 data++;
158 data += components;
160 data += line;
163 else if (width > 1 && height == 1) {
164 if (filter)
165 for (y = 0; y < height; y += 1) {
166 for (x = 0; x < width; x += 2) {
167 for (c = 0; c < components; c++) {
168 *d++ = (GET(0)+GET(components)) / 2;
169 data++;
171 data += components;
174 else
175 for (y = 0; y < height; y += 1) {
176 for (x = 0; x < width; x += 2) {
177 for (c = 0; c < components; c++) {
178 *d++ = GET(0);
179 data++;
181 data += components;
185 else if (width == 1 && height > 1) {
186 if (filter)
187 for (y = 0; y < height; y += 2) {
188 for (x = 0; x < width; x += 1) {
189 for (c = 0; c < components; c++) {
190 *d++ = (GET(0)+GET(line)) / 2;
191 data++;
194 data += line;
196 else
197 for (y = 0; y < height; y += 2) {
198 for (x = 0; x < width; x += 1) {
199 for (c = 0; c < components; c++) {
200 *d++ = GET(0);
201 data++;
204 data += line;
207 else {
208 return 0;
211 return 1;
214 #undef GET
216 /* Replacement for gluBuild2DMipmaps so GLU isn't needed */
217 static void Build2DMipmaps(GLint components, GLint width, GLint height, GLenum format, const unsigned char *data, int filter) {
218 int level = 0;
219 unsigned char *d = (unsigned char *) malloc((width/2)*(height/2)*components+4);
220 const unsigned char *last = data;
222 glTexImage2D(GL_TEXTURE_2D, level, components, width, height, 0, format, GL_UNSIGNED_BYTE, data);
223 level++;
225 while (HalfSize(components, width, height, last, d, filter)) {
226 if (width > 1) width /= 2;
227 if (height > 1) height /= 2;
229 glTexImage2D(GL_TEXTURE_2D, level, components, width, height, 0, format, GL_UNSIGNED_BYTE, d);
230 level++;
231 last = d;
234 free(d);
237 int APIENTRY pngLoadRaw(const char *filename, pngRawInfo *pinfo) {
238 int result;
239 FILE *fp = fopen(filename, "rb");
240 if (fp == NULL) return 0;
242 result = pngLoadRawF(fp, pinfo);
244 if (fclose(fp) != 0) {
245 if (result) {
246 free(pinfo->Data);
247 free(pinfo->Palette);
249 return 0;
252 return result;
255 int APIENTRY pngLoadRawF(FILE *fp, pngRawInfo *pinfo) {
256 unsigned char header[8];
257 png_structp png;
258 png_infop info;
259 png_infop endinfo;
260 png_bytep data;
261 png_bytep *row_p;
262 double fileGamma;
264 png_uint_32 width, height;
265 int depth, color;
267 png_uint_32 i;
269 if (pinfo == NULL) return 0;
271 fread(header, 1, 8, fp);
272 if (!png_check_sig(header, 8)) return 0;
274 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
275 info = png_create_info_struct(png);
276 endinfo = png_create_info_struct(png);
278 // DH: added following lines
279 if (setjmp(png->jmpbuf))
281 png_destroy_read_struct(&png, &info, &endinfo);
282 return 0;
284 // ~DH
286 png_init_io(png, fp);
287 png_set_sig_bytes(png, 8);
288 png_read_info(png, info);
289 png_get_IHDR(png, info, &width, &height, &depth, &color, NULL, NULL, NULL);
291 pinfo->Width = width;
292 pinfo->Height = height;
293 pinfo->Depth = depth;
295 /*--GAMMA--*/
296 checkForGammaEnv();
297 if (png_get_gAMA(png, info, &fileGamma))
298 png_set_gamma(png, screenGamma, fileGamma);
299 else
300 png_set_gamma(png, screenGamma, 1.0/2.2);
302 png_read_update_info(png, info);
304 data = (png_bytep) malloc(png_get_rowbytes(png, info)*height);
305 row_p = (png_bytep *) malloc(sizeof(png_bytep)*height);
307 for (i = 0; i < height; i++) {
308 if (StandardOrientation)
309 row_p[height - 1 - i] = &data[png_get_rowbytes(png, info)*i];
310 else
311 row_p[i] = &data[png_get_rowbytes(png, info)*i];
314 png_read_image(png, row_p);
315 free(row_p);
317 if (color == PNG_COLOR_TYPE_PALETTE) {
318 int cols;
319 png_get_PLTE(png, info, (png_colorp *) &pinfo->Palette, &cols);
321 else {
322 pinfo->Palette = NULL;
325 if (color&PNG_COLOR_MASK_ALPHA) {
326 if (color&PNG_COLOR_MASK_PALETTE || color == PNG_COLOR_TYPE_GRAY_ALPHA)
327 pinfo->Components = 2;
328 else
329 pinfo->Components = 4;
330 pinfo->Alpha = 8;
332 else {
333 if (color&PNG_COLOR_MASK_PALETTE || color == PNG_COLOR_TYPE_GRAY)
334 pinfo->Components = 1;
335 else
336 pinfo->Components = 3;
337 pinfo->Alpha = 0;
340 pinfo->Data = data;
342 png_read_end(png, endinfo);
343 png_destroy_read_struct(&png, &info, &endinfo);
345 return 1;
348 int APIENTRY pngLoad(const char *filename, int mipmap, int trans, pngInfo *pinfo) {
349 int result;
350 FILE *fp = fopen(filename, "rb");
351 if (fp == NULL) return 0;
353 result = pngLoadF(fp, mipmap, trans, pinfo);
355 if (fclose(fp) != 0) return 0;
357 return result;
360 int APIENTRY pngLoadF(FILE *fp, int mipmap, int trans, pngInfo *pinfo) {
361 GLint pack, unpack;
362 unsigned char header[8];
363 png_structp png;
364 png_infop info;
365 png_infop endinfo;
366 png_bytep data, data2;
367 png_bytep *row_p;
368 double fileGamma;
370 png_uint_32 width, height, rw, rh;
371 int depth, color;
373 png_uint_32 i;
375 fread(header, 1, 8, fp);
376 if (!png_check_sig(header, 8)) return 0;
378 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
379 info = png_create_info_struct(png);
380 endinfo = png_create_info_struct(png);
382 // DH: added following lines
383 if (setjmp(png->jmpbuf))
385 png_destroy_read_struct(&png, &info, &endinfo);
386 return 0;
388 // ~DH
390 png_init_io(png, fp);
391 png_set_sig_bytes(png, 8);
392 png_read_info(png, info);
393 png_get_IHDR(png, info, &width, &height, &depth, &color, NULL, NULL, NULL);
395 if (pinfo != NULL) {
396 pinfo->Width = width;
397 pinfo->Height = height;
398 pinfo->Depth = depth;
401 if (MaxTextureSize == 0)
402 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &MaxTextureSize);
404 #ifdef SUPPORTS_PALETTE_EXT
405 #ifdef _WIN32
406 if (PalettedTextures == -1)
407 PalettedTextures = ExtSupported("GL_EXT_paletted_texture") && (strstr((const char *) glGetString(GL_VERSION), "1.1.0 3Dfx Beta") == NULL);
409 if (PalettedTextures) {
410 if (glColorTableEXT == NULL) {
411 glColorTableEXT = (PFNGLCOLORTABLEEXTPROC) wglGetProcAddress("glColorTableEXT");
412 if (glColorTableEXT == NULL)
413 PalettedTextures = 0;
416 #endif
417 #endif
419 if (PalettedTextures == -1)
420 PalettedTextures = 0;
422 if (color == PNG_COLOR_TYPE_GRAY || color == PNG_COLOR_TYPE_GRAY_ALPHA)
423 png_set_gray_to_rgb(png);
425 if (color&PNG_COLOR_MASK_ALPHA && trans != PNG_ALPHA) {
426 png_set_strip_alpha(png);
427 color &= ~PNG_COLOR_MASK_ALPHA;
430 if (!(PalettedTextures && mipmap >= 0 && trans == PNG_SOLID))
431 if (color == PNG_COLOR_TYPE_PALETTE)
432 png_set_expand(png);
434 /*--GAMMA--*/
435 checkForGammaEnv();
436 if (png_get_gAMA(png, info, &fileGamma))
437 png_set_gamma(png, screenGamma, fileGamma);
438 else
439 png_set_gamma(png, screenGamma, 1.0/2.2);
441 png_read_update_info(png, info);
443 data = (png_bytep) malloc(png_get_rowbytes(png, info)*height);
444 row_p = (png_bytep *) malloc(sizeof(png_bytep)*height);
446 for (i = 0; i < height; i++) {
447 if (StandardOrientation)
448 row_p[height - 1 - i] = &data[png_get_rowbytes(png, info)*i];
449 else
450 row_p[i] = &data[png_get_rowbytes(png, info)*i];
453 png_read_image(png, row_p);
454 free(row_p);
456 rw = SafeSize(width), rh = SafeSize(height);
458 if (rw != width || rh != height) {
459 const int channels = png_get_rowbytes(png, info)/width;
461 data2 = (png_bytep) malloc(rw*rh*channels);
463 /* Doesn't work on certain sizes */
464 /* if (gluScaleImage(glformat, width, height, GL_UNSIGNED_BYTE, data, rw, rh, GL_UNSIGNED_BYTE, data2) != 0)
465 return 0;
467 Resize(channels, data, width, height, data2, rw, rh);
469 width = rw, height = rh;
470 free(data);
471 data = data2;
474 { /* OpenGL stuff */
475 glGetIntegerv(GL_PACK_ALIGNMENT, &pack);
476 glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack);
477 glPixelStorei(GL_PACK_ALIGNMENT, 1);
478 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
480 #ifdef SUPPORTS_PALETTE_EXT
481 if (PalettedTextures && mipmap >= 0 && trans == PNG_SOLID && color == PNG_COLOR_TYPE_PALETTE) {
482 png_colorp pal;
483 int cols;
484 GLint intf;
486 if (pinfo != NULL) pinfo->Alpha = 0;
487 png_get_PLTE(png, info, &pal, &cols);
489 switch (cols) {
490 case 1<<1: intf = GL_COLOR_INDEX1_EXT; break;
491 case 1<<2: intf = GL_COLOR_INDEX2_EXT; break;
492 case 1<<4: intf = GL_COLOR_INDEX4_EXT; break;
493 case 1<<8: intf = GL_COLOR_INDEX8_EXT; break;
494 case 1<<12: intf = GL_COLOR_INDEX12_EXT; break;
495 case 1<<16: intf = GL_COLOR_INDEX16_EXT; break;
496 default:
497 /*printf("Warning: Colour depth %i not recognised\n", cols);*/
498 return 0;
500 glColorTableEXT(GL_TEXTURE_2D, GL_RGB8, cols, GL_RGB, GL_UNSIGNED_BYTE, pal);
501 glTexImage2D(GL_TEXTURE_2D, mipmap, intf, width, height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, data);
503 else
504 #endif
505 if (trans == PNG_SOLID || trans == PNG_ALPHA || color == PNG_COLOR_TYPE_RGB_ALPHA || color == PNG_COLOR_TYPE_GRAY_ALPHA) {
506 GLenum glformat;
507 GLint glcomponent;
509 switch (color) {
510 case PNG_COLOR_TYPE_GRAY:
511 case PNG_COLOR_TYPE_RGB:
512 case PNG_COLOR_TYPE_PALETTE:
513 glformat = GL_RGB;
514 glcomponent = 3;
515 if (pinfo != NULL) pinfo->Alpha = 0;
516 break;
518 case PNG_COLOR_TYPE_GRAY_ALPHA:
519 case PNG_COLOR_TYPE_RGB_ALPHA:
520 glformat = GL_RGBA;
521 glcomponent = 4;
522 if (pinfo != NULL) pinfo->Alpha = 8;
523 break;
525 default:
526 /*puts("glformat not set");*/
527 return 0;
530 if (mipmap == PNG_BUILDMIPMAPS)
531 Build2DMipmaps(glcomponent, width, height, glformat, data, 1);
532 else if (mipmap == PNG_SIMPLEMIPMAPS)
533 Build2DMipmaps(glcomponent, width, height, glformat, data, 0);
534 else
535 glTexImage2D(GL_TEXTURE_2D, mipmap, glcomponent, width, height, 0, glformat, GL_UNSIGNED_BYTE, data);
537 else {
538 png_bytep p, endp, q;
539 int r, g, b, a;
541 p = data, endp = p+width*height*3;
542 q = data2 = (png_bytep) malloc(sizeof(png_byte)*width*height*4);
544 if (pinfo != NULL) pinfo->Alpha = 8;
546 #define FORSTART \
547 do { \
548 r = *p++; /*red */ \
549 g = *p++; /*green*/ \
550 b = *p++; /*blue */ \
551 *q++ = r; \
552 *q++ = g; \
553 *q++ = b;
555 #define FOREND \
556 q++; \
557 } while (p != endp);
559 #define ALPHA *q
561 switch (trans) {
562 case PNG_CALLBACK:
563 FORSTART
564 ALPHA = AlphaCallback((unsigned char) r, (unsigned char) g, (unsigned char) b);
565 FOREND
566 break;
568 case PNG_STENCIL:
569 FORSTART
570 if (r == StencilRed && g == StencilGreen && b == StencilBlue)
571 ALPHA = 0;
572 else
573 ALPHA = 255;
574 FOREND
575 break;
577 case PNG_BLEND1:
578 FORSTART
579 a = r+g+b;
580 if (a > 255) ALPHA = 255; else ALPHA = a;
581 FOREND
582 break;
584 case PNG_BLEND2:
585 FORSTART
586 a = r+g+b;
587 if (a > 255*2) ALPHA = 255; else ALPHA = a/2;
588 FOREND
589 break;
591 case PNG_BLEND3:
592 FORSTART
593 ALPHA = (r+g+b)/3;
594 FOREND
595 break;
597 case PNG_BLEND4:
598 FORSTART
599 a = r*r+g*g+b*b;
600 if (a > 255) ALPHA = 255; else ALPHA = a;
601 FOREND
602 break;
604 case PNG_BLEND5:
605 FORSTART
606 a = r*r+g*g+b*b;
607 if (a > 255*2) ALPHA = 255; else ALPHA = a/2;
608 FOREND
609 break;
611 case PNG_BLEND6:
612 FORSTART
613 a = r*r+g*g+b*b;
614 if (a > 255*3) ALPHA = 255; else ALPHA = a/3;
615 FOREND
616 break;
618 case PNG_BLEND7:
619 FORSTART
620 a = r*r+g*g+b*b;
621 if (a > 255*255) ALPHA = 255; else ALPHA = (int) sqrt(a);
622 FOREND
623 break;
626 #undef FORSTART
627 #undef FOREND
628 #undef ALPHA
630 if (mipmap == PNG_BUILDMIPMAPS)
631 Build2DMipmaps(4, width, height, GL_RGBA, data2, 1);
632 else if (mipmap == PNG_SIMPLEMIPMAPS)
633 Build2DMipmaps(4, width, height, GL_RGBA, data2, 0);
634 else
635 glTexImage2D(GL_TEXTURE_2D, mipmap, 4, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);
637 free(data2);
640 glPixelStorei(GL_PACK_ALIGNMENT, pack);
641 glPixelStorei(GL_UNPACK_ALIGNMENT, unpack);
642 } /* OpenGL end */
644 png_read_end(png, endinfo);
645 png_destroy_read_struct(&png, &info, &endinfo);
647 free(data);
649 return 1;
652 static unsigned int SetParams(int wrapst, int magfilter, int minfilter) {
653 unsigned int id;
655 glGenTextures(1, &id);
656 glBindTexture(GL_TEXTURE_2D, id);
658 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapst);
659 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapst);
661 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magfilter);
662 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minfilter);
664 return id;
667 unsigned int APIENTRY pngBind(const char *filename, int mipmap, int trans, pngInfo *info, int wrapst, int minfilter, int magfilter) {
668 unsigned int id = SetParams(wrapst, magfilter, minfilter);
670 if (id != 0 && pngLoad(filename, mipmap, trans, info))
671 return id;
672 return 0;
675 unsigned int APIENTRY pngBindF(FILE *file, int mipmap, int trans, pngInfo *info, int wrapst, int minfilter, int magfilter) {
676 unsigned int id = SetParams(wrapst, magfilter, minfilter);
678 if (id != 0 && pngLoadF(file, mipmap, trans, info))
679 return id;
680 return 0;
683 void APIENTRY pngSetStencil(unsigned char red, unsigned char green, unsigned char blue) {
684 StencilRed = red, StencilGreen = green, StencilBlue = blue;
687 void APIENTRY pngSetAlphaCallback(unsigned char (*callback)(unsigned char red, unsigned char green, unsigned char blue)) {
688 if (callback == NULL)
689 AlphaCallback = DefaultAlphaCallback;
690 else
691 AlphaCallback = callback;
694 void APIENTRY pngSetViewingGamma(double viewingGamma) {
695 if(viewingGamma > 0) {
696 gammaExplicit = 1;
697 screenGamma = 2.2/viewingGamma;
699 else {
700 gammaExplicit = 0;
701 screenGamma = 2.2;
705 void APIENTRY pngSetStandardOrientation(int standardorientation) {
706 StandardOrientation = standardorientation;