//======================================================================== // GLFW - An OpenGL framework // File: image.c // Platform: Any // API version: 2.6 // WWW: http://glfw.sourceforge.net //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Camilla Berglund // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== //======================================================================== // Description: // // This module acts as an interface for different image file formats (the // image file format is detected automatically). // // By default the loaded image is rescaled (using bilinear interpolation) // to the next higher 2^N x 2^M resolution, unless it has a valid // 2^N x 2^M resolution. The interpolation is quite slow, even if the // routine has been optimized for speed (a 200x200 RGB image is scaled to // 256x256 in ~30 ms on a P3-500). // // Paletted images are converted to RGB/RGBA images. // // A convenience function is also included (glfwLoadTexture2D), which // loads a texture image from a file directly to OpenGL texture memory, // with an option to generate all mipmap levels. GL_SGIS_generate_mipmap // is used whenever available, which should give an optimal mipmap // generation speed (possibly performed in hardware). A software fallback // method is included when GL_SGIS_generate_mipmap is not supported (it // generates all mipmaps of a 256x256 RGB texture in ~3 ms on a P3-500). // //======================================================================== #include "internal.h" // We want to support automatic mipmap generation #ifndef GL_SGIS_generate_mipmap #define GL_GENERATE_MIPMAP_SGIS 0x8191 #define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 #define GL_SGIS_generate_mipmap 1 #endif // GL_SGIS_generate_mipmap //************************************************************************ //**** GLFW internal functions **** //************************************************************************ //======================================================================== // _glfwUpsampleImage() - Upsample image, from size w1 x h1 to w2 x h2 //======================================================================== static void _glfwUpsampleImage( unsigned char *src, unsigned char *dst, int w1, int h1, int w2, int h2, int bpp ) { int m, n, k, x, y, col8; float dx, dy, xstep, ystep, col, col1, col2; unsigned char *src1, *src2, *src3, *src4; // Calculate scaling factor xstep = (float)(w1-1) / (float)(w2-1); ystep = (float)(h1-1) / (float)(h2-1); // Copy source data to destination data with bilinear interpolation // Note: The rather strange look of this routine is a direct result of // my attempts at optimizing it. Improvements are welcome! dy = 0.0f; y = 0; for( n = 0; n < h2; n ++ ) { dx = 0.0f; src1 = &src[ y*w1*bpp ]; src3 = y < (h1-1) ? src1 + w1*bpp : src1; src2 = src1 + bpp; src4 = src3 + bpp; x = 0; for( m = 0; m < w2; m ++ ) { for( k = 0; k < bpp; k ++ ) { col1 = *src1 ++; col2 = *src2 ++; col = col1 + (col2 - col1) * dx; col1 = *src3 ++; col2 = *src4 ++; col2 = col1 + (col2 - col1) * dx; col += (col2 - col) * dy; col8 = (int) (col + 0.5); if( col8 >= 256 ) col8 = 255; *dst++ = (unsigned char) col8; } dx += xstep; if( dx >= 1.0f ) { x ++; dx -= 1.0f; if( x >= (w1-1) ) { src2 = src1; src4 = src3; } } else { src1 -= bpp; src2 -= bpp; src3 -= bpp; src4 -= bpp; } } dy += ystep; if( dy >= 1.0f ) { y ++; dy -= 1.0f; } } } //======================================================================== // _glfwHalveImage() - Build the next mip-map level //======================================================================== static int _glfwHalveImage( GLubyte *src, int *width, int *height, int components ) { int halfwidth, halfheight, m, n, k, idx1, idx2; GLubyte *dst; // Last level? if( *width <= 1 && *height <= 1 ) { return GL_FALSE; } // Calculate new width and height (handle 1D case) halfwidth = *width > 1 ? *width / 2 : 1; halfheight = *height > 1 ? *height / 2 : 1; // Downsample image with a simple box-filter dst = src; if( *width == 1 || *height == 1 ) { // 1D case for( m = 0; m < halfwidth+halfheight-1; m ++ ) { for( k = 0; k < components; k ++ ) { *dst ++ = (GLubyte) (((int)*src + (int)src[components] + 1) >> 1); src ++; } src += components; } } else { // 2D case idx1 = *width*components; idx2 = (*width+1)*components; for( m = 0; m < halfheight; m ++ ) { for( n = 0; n < halfwidth; n ++ ) { for( k = 0; k < components; k ++ ) { *dst ++ = (GLubyte) (((int)*src + (int)src[components] + (int)src[idx1] + (int)src[idx2] + 2) >> 2); src ++; } src += components; } src += components * (*width); } } // Return new width and height *width = halfwidth; *height = halfheight; return GL_TRUE; } //======================================================================== // _glfwRescaleImage() - Rescales an image into power-of-two dimensions //======================================================================== static int _glfwRescaleImage( GLFWimage* image ) { int width, height, log2, newsize; unsigned char *data; // Calculate next larger 2^N width for( log2 = 0, width = image->Width; width > 1; width >>= 1, log2 ++ ) ; width = (int) 1 << log2; if( width < image->Width ) { width <<= 1; } // Calculate next larger 2^M height for( log2 = 0, height = image->Height; height > 1; height >>= 1, log2 ++ ) ; height = (int) 1 << log2; if( height < image->Height ) { height <<= 1; } // Do we really need to rescale? if( width != image->Width || height != image->Height ) { // Allocate memory for new (upsampled) image data newsize = width * height * image->BytesPerPixel; data = (unsigned char *) malloc( newsize ); if( data == NULL ) { free( image->Data ); return GL_FALSE; } // Copy old image data to new image data with interpolation _glfwUpsampleImage( image->Data, data, image->Width, image->Height, width, height, image->BytesPerPixel ); // Free memory for old image data (not needed anymore) free( image->Data ); // Set pointer to new image data, and set new image dimensions image->Data = data; image->Width = width; image->Height = height; } return GL_TRUE; } //************************************************************************ //**** GLFW user functions **** //************************************************************************ //======================================================================== // glfwReadImage() - Read an image from a named file //======================================================================== GLFWAPI int GLFWAPIENTRY glfwReadImage( const char *name, GLFWimage *img, int flags ) { _GLFWstream stream; // Is GLFW initialized? if( !_glfwInitialized ) { return GL_FALSE; } // Start with an empty image descriptor img->Width = 0; img->Height = 0; img->BytesPerPixel = 0; img->Data = NULL; // Open file if( !_glfwOpenFileStream( &stream, name, "rb" ) ) { return GL_FALSE; } // We only support TGA files at the moment if( !_glfwReadTGA( &stream, img, flags ) ) { _glfwCloseStream( &stream ); return GL_FALSE; } // Close stream _glfwCloseStream( &stream ); // Should we rescale the image to closest 2^N x 2^M resolution? if( !(flags & GLFW_NO_RESCALE_BIT) ) { if( !_glfwRescaleImage( img ) ) { return GL_FALSE; } } // Interpret BytesPerPixel as an OpenGL format switch( img->BytesPerPixel ) { default: case 1: if( flags & GLFW_ALPHA_MAP_BIT ) { img->Format = GL_ALPHA; } else { img->Format = GL_LUMINANCE; } break; case 3: img->Format = GL_RGB; break; case 4: img->Format = GL_RGBA; break; } return GL_TRUE; } //======================================================================== // glfwReadMemoryImage() - Read an image file from a memory buffer //======================================================================== GLFWAPI int GLFWAPIENTRY glfwReadMemoryImage( const void *data, long size, GLFWimage *img, int flags ) { _GLFWstream stream; // Is GLFW initialized? if( !_glfwInitialized ) { return GL_FALSE; } // Start with an empty image descriptor img->Width = 0; img->Height = 0; img->BytesPerPixel = 0; img->Data = NULL; // Open buffer if( !_glfwOpenBufferStream( &stream, (void*) data, size ) ) { return GL_FALSE; } // We only support TGA files at the moment if( !_glfwReadTGA( &stream, img, flags ) ) { _glfwCloseStream( &stream ); return GL_FALSE; } // Close stream _glfwCloseStream( &stream ); // Should we rescale the image to closest 2^N x 2^M resolution? if( !(flags & GLFW_NO_RESCALE_BIT) ) { if( !_glfwRescaleImage( img ) ) { return GL_FALSE; } } // Interpret BytesPerPixel as an OpenGL format switch( img->BytesPerPixel ) { default: case 1: if( flags & GLFW_ALPHA_MAP_BIT ) { img->Format = GL_ALPHA; } else { img->Format = GL_LUMINANCE; } break; case 3: img->Format = GL_RGB; break; case 4: img->Format = GL_RGBA; break; } return GL_TRUE; } //======================================================================== // glfwFreeImage() - Free allocated memory for an image //======================================================================== GLFWAPI void GLFWAPIENTRY glfwFreeImage( GLFWimage *img ) { // Is GLFW initialized? if( !_glfwInitialized ) { return; } // Free memory if( img->Data != NULL ) { free( img->Data ); img->Data = NULL; } // Clear all fields img->Width = 0; img->Height = 0; img->Format = 0; img->BytesPerPixel = 0; } //======================================================================== // glfwLoadTexture2D() - Read an image from a file, and upload it to // texture memory //======================================================================== GLFWAPI int GLFWAPIENTRY glfwLoadTexture2D( const char *name, int flags ) { GLFWimage img; // Is GLFW initialized? if( !_glfwInitialized || !_glfwWin.Opened ) { return GL_FALSE; } // Force rescaling if necessary if( !_glfwWin.Has_GL_ARB_texture_non_power_of_two ) { flags &= (~GLFW_NO_RESCALE_BIT); } // Read image from file if( !glfwReadImage( name, &img, flags ) ) { return GL_FALSE; } if( !glfwLoadTextureImage2D( &img, flags ) ) { return GL_FALSE; } // Data buffer is not needed anymore glfwFreeImage( &img ); return GL_TRUE; } //======================================================================== // glfwLoadMemoryTexture2D() - Read an image from a buffer, and upload it to // texture memory //======================================================================== GLFWAPI int GLFWAPIENTRY glfwLoadMemoryTexture2D( const void *data, long size, int flags ) { GLFWimage img; // Is GLFW initialized? if( !_glfwInitialized || !_glfwWin.Opened ) { return GL_FALSE; } // Force rescaling if necessary if( !_glfwWin.Has_GL_ARB_texture_non_power_of_two ) { flags &= (~GLFW_NO_RESCALE_BIT); } // Read image from file if( !glfwReadMemoryImage( data, size, &img, flags ) ) { return GL_FALSE; } if( !glfwLoadTextureImage2D( &img, flags ) ) { return GL_FALSE; } // Data buffer is not needed anymore glfwFreeImage( &img ); return GL_TRUE; } //======================================================================== // glfwLoadTextureImage2D() - Upload an image object to texture memory //======================================================================== GLFWAPI int GLFWAPIENTRY glfwLoadTextureImage2D( GLFWimage *img, int flags ) { GLint UnpackAlignment, GenMipMap; int level, format, AutoGen, newsize, n; unsigned char *data, *dataptr; // Is GLFW initialized? if( !_glfwInitialized || !_glfwWin.Opened ) { return GL_FALSE; } // TODO: Use GL_MAX_TEXTURE_SIZE or GL_PROXY_TEXTURE_2D to determine // whether the image size is valid. // NOTE: May require box filter downsampling routine. // Do we need to convert the alpha map to RGBA format (OpenGL 1.0)? if( (_glfwWin.GLVerMajor == 1) && (_glfwWin.GLVerMinor == 0) && (img->Format == GL_ALPHA) ) { // We go to RGBA representation instead img->BytesPerPixel = 4; // Allocate memory for new RGBA image data newsize = img->Width * img->Height * img->BytesPerPixel; data = (unsigned char *) malloc( newsize ); if( data == NULL ) { free( img->Data ); return GL_FALSE; } // Convert Alpha map to RGBA dataptr = data; for( n = 0; n < (img->Width*img->Height); ++ n ) { *dataptr ++ = 255; *dataptr ++ = 255; *dataptr ++ = 255; *dataptr ++ = img->Data[n]; } // Free memory for old image data (not needed anymore) free( img->Data ); // Set pointer to new image data img->Data = data; } // Set unpack alignment to one byte glGetIntegerv( GL_UNPACK_ALIGNMENT, &UnpackAlignment ); glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); // Should we use automatic mipmap generation? AutoGen = ( flags & GLFW_BUILD_MIPMAPS_BIT ) && _glfwWin.Has_GL_SGIS_generate_mipmap; // Enable automatic mipmap generation if( AutoGen ) { glGetTexParameteriv( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, &GenMipMap ); glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); } // Format specification is different for OpenGL 1.0 if( _glfwWin.GLVerMajor == 1 && _glfwWin.GLVerMinor == 0 ) { format = img->BytesPerPixel; } else { format = img->Format; } // Upload to texture memeory level = 0; do { // Upload this mipmap level glTexImage2D( GL_TEXTURE_2D, level, format, img->Width, img->Height, 0, format, GL_UNSIGNED_BYTE, (void*) img->Data ); // Build next mipmap level manually, if required if( ( flags & GLFW_BUILD_MIPMAPS_BIT ) && !AutoGen ) { level = _glfwHalveImage( img->Data, &img->Width, &img->Height, img->BytesPerPixel ) ? level + 1 : 0; } } while( level != 0 ); // Restore old automatic mipmap generation state if( AutoGen ) { glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GenMipMap ); } // Restore old unpack alignment glPixelStorei( GL_UNPACK_ALIGNMENT, UnpackAlignment ); return GL_TRUE; }