Subversion Repositories gelsvn

Rev

Rev 603 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
        Jonathan Dummer
        2007-07-26-10.36

        Simple OpenGL Image Library

        Public Domain
        using Sean Barret's stb_image as a base

        Thanks to:
        * Sean Barret - for the awesome stb_image
        * Dan Venkitachalam - for finding some non-compliant DDS files, and patching some explicit casts
        * everybody at gamedev.net
*/

#define SOIL_CHECK_FOR_GL_ERRORS 0

#ifdef WIN32
        #define WIN32_LEAN_AND_MEAN
        #include <windows.h>
        #include <wingdi.h>
        #include <GL/gl.h>
#elif defined(__APPLE__) || defined(__APPLE_CC__)
        /*      I can't test this Apple stuff!  */
        #include <OpenGL/gl.h>
        #include <Carbon/Carbon.h>
        #define APIENTRY
#else

        #include <GL/gl.h>
        #include <GL/glx.h>
#endif


#include "SOIL.h"
#include "stb_image.h"
#include "stb_image_write.h"
#include "image_helper.h"
#include "image_DXT.h"

#include <stdlib.h>
#include <string.h>

/*      error reporting */
const char *result_string_pointer = "SOIL initialized";

/*      for loading cube maps   */
enum{
        SOIL_CAPABILITY_UNKNOWN = -1,
        SOIL_CAPABILITY_NONE = 0,
        SOIL_CAPABILITY_PRESENT = 1
};
static int has_cubemap_capability = SOIL_CAPABILITY_UNKNOWN;
int query_cubemap_capability( void );
#define SOIL_TEXTURE_WRAP_R                                     0x8072
#define SOIL_CLAMP_TO_EDGE                                      0x812F
#define SOIL_NORMAL_MAP                                         0x8511
#define SOIL_REFLECTION_MAP                                     0x8512
#define SOIL_TEXTURE_CUBE_MAP                           0x8513
#define SOIL_TEXTURE_BINDING_CUBE_MAP           0x8514
#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_X        0x8515
#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X        0x8516
#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y        0x8517
#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y        0x8518
#define SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z        0x8519
#define SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z        0x851A
#define SOIL_PROXY_TEXTURE_CUBE_MAP                     0x851B
#define SOIL_MAX_CUBE_MAP_TEXTURE_SIZE          0x851C
/*      for non-power-of-two texture    */
static int has_NPOT_capability = SOIL_CAPABILITY_UNKNOWN;
int query_NPOT_capability( void );
/*      for texture rectangles  */
static int has_tex_rectangle_capability = SOIL_CAPABILITY_UNKNOWN;
int query_tex_rectangle_capability( void );
#define SOIL_TEXTURE_RECTANGLE_ARB                              0x84F5
#define SOIL_MAX_RECTANGLE_TEXTURE_SIZE_ARB             0x84F8
/*      for using DXT compression       */
static int has_DXT_capability = SOIL_CAPABILITY_UNKNOWN;
int query_DXT_capability( void );
#define SOIL_RGB_S3TC_DXT1              0x83F0
#define SOIL_RGBA_S3TC_DXT1             0x83F1
#define SOIL_RGBA_S3TC_DXT3             0x83F2
#define SOIL_RGBA_S3TC_DXT5             0x83F3
typedef void (APIENTRY * P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid * data);
P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC soilGlCompressedTexImage2D = NULL;
unsigned int SOIL_direct_load_DDS(
                const char *filename,
                unsigned int reuse_texture_ID,
                int flags,
                int loading_as_cubemap );
unsigned int SOIL_direct_load_DDS_from_memory(
                const unsigned char *const buffer,
                int buffer_length,
                unsigned int reuse_texture_ID,
                int flags,
                int loading_as_cubemap );
/*      other functions */
unsigned int
        SOIL_internal_create_OGL_texture
        (
                const unsigned char *const data,
                int width, int height, int channels,
                unsigned int reuse_texture_ID,
                unsigned int flags,
                unsigned int opengl_texture_type,
                unsigned int opengl_texture_target,
                unsigned int texture_check_size_enum
        );

/*      and the code magic begins here [8^)     */
unsigned int
        SOIL_load_OGL_texture
        (
                const char *filename,
                int force_channels,
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      variables       */
        unsigned char* img;
        int width, height, channels;
        unsigned int tex_id;
        /*      does the user want direct uploading of the image as a DDS file? */
        if( flags & SOIL_FLAG_DDS_LOAD_DIRECT )
        {
                /*      1st try direct loading of the image as a DDS file
                        note: direct uploading will only load what is in the
                        DDS file, no MIPmaps will be generated, the image will
                        not be flipped, etc.    */
                tex_id = SOIL_direct_load_DDS( filename, reuse_texture_ID, flags, 0 );
                if( tex_id )
                {
                        /*      hey, it worked!!        */
                        return tex_id;
                }
        }
        /*      try to load the image   */
        img = SOIL_load_image( filename, &width, &height, &channels, force_channels );
        /*      channels holds the original number of channels, which may have been forced      */
        if( (force_channels >= 1) && (force_channels <= 4) )
        {
                channels = force_channels;
        }
        if( NULL == img )
        {
                /*      image loading failed    */
                result_string_pointer = stbi_failure_reason();
                return 0;
        }
        /*      OK, make it a texture!  */
        tex_id = SOIL_internal_create_OGL_texture(
                        img, width, height, channels,
                        reuse_texture_ID, flags,
                        GL_TEXTURE_2D, GL_TEXTURE_2D,
                        GL_MAX_TEXTURE_SIZE );
        /*      and nuke the image data */
        SOIL_free_image_data( img );
        /*      and return the handle, such as it is    */
        return tex_id;
}

unsigned int
        SOIL_load_OGL_HDR_texture
        (
                const char *filename,
                int fake_HDR_format,
                int rescale_to_max,
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      variables       */
        unsigned char* img;
        int width, height, channels;
        unsigned int tex_id;
        /*      no direct uploading of the image as a DDS file  */
        /* error check */
        if( (fake_HDR_format != SOIL_HDR_RGBE) &&
                (fake_HDR_format != SOIL_HDR_RGBdivA) &&
                (fake_HDR_format != SOIL_HDR_RGBdivA2) )
        {
                result_string_pointer = "Invalid fake HDR format specified";
                return 0;
        }
        /*      try to load the image (only the HDR type) */
        img = stbi_hdr_load_rgbe( filename, &width, &height, &channels, 4 );
        /*      channels holds the original number of channels, which may have been forced      */
        if( NULL == img )
        {
                /*      image loading failed    */
                result_string_pointer = stbi_failure_reason();
                return 0;
        }
        /* the load worked, do I need to convert it? */
        if( fake_HDR_format == SOIL_HDR_RGBdivA )
        {
                RGBE_to_RGBdivA( img, width, height, rescale_to_max );
        } else if( fake_HDR_format == SOIL_HDR_RGBdivA2 )
        {
                RGBE_to_RGBdivA2( img, width, height, rescale_to_max );
        }
        /*      OK, make it a texture!  */
        tex_id = SOIL_internal_create_OGL_texture(
                        img, width, height, channels,
                        reuse_texture_ID, flags,
                        GL_TEXTURE_2D, GL_TEXTURE_2D,
                        GL_MAX_TEXTURE_SIZE );
        /*      and nuke the image data */
        SOIL_free_image_data( img );
        /*      and return the handle, such as it is    */
        return tex_id;
}

unsigned int
        SOIL_load_OGL_texture_from_memory
        (
                const unsigned char *const buffer,
                int buffer_length,
                int force_channels,
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      variables       */
        unsigned char* img;
        int width, height, channels;
        unsigned int tex_id;
        /*      does the user want direct uploading of the image as a DDS file? */
        if( flags & SOIL_FLAG_DDS_LOAD_DIRECT )
        {
                /*      1st try direct loading of the image as a DDS file
                        note: direct uploading will only load what is in the
                        DDS file, no MIPmaps will be generated, the image will
                        not be flipped, etc.    */
                tex_id = SOIL_direct_load_DDS_from_memory(
                                buffer, buffer_length,
                                reuse_texture_ID, flags, 0 );
                if( tex_id )
                {
                        /*      hey, it worked!!        */
                        return tex_id;
                }
        }
        /*      try to load the image   */
        img = SOIL_load_image_from_memory(
                                        buffer, buffer_length,
                                        &width, &height, &channels,
                                        force_channels );
        /*      channels holds the original number of channels, which may have been forced      */
        if( (force_channels >= 1) && (force_channels <= 4) )
        {
                channels = force_channels;
        }
        if( NULL == img )
        {
                /*      image loading failed    */
                result_string_pointer = stbi_failure_reason();
                return 0;
        }
        /*      OK, make it a texture!  */
        tex_id = SOIL_internal_create_OGL_texture(
                        img, width, height, channels,
                        reuse_texture_ID, flags,
                        GL_TEXTURE_2D, GL_TEXTURE_2D,
                        GL_MAX_TEXTURE_SIZE );
        /*      and nuke the image data */
        SOIL_free_image_data( img );
        /*      and return the handle, such as it is    */
        return tex_id;
}

unsigned int
        SOIL_load_OGL_cubemap
        (
                const char *x_pos_file,
                const char *x_neg_file,
                const char *y_pos_file,
                const char *y_neg_file,
                const char *z_pos_file,
                const char *z_neg_file,
                int force_channels,
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      variables       */
        unsigned char* img;
        int width, height, channels;
        unsigned int tex_id;
        /*      error checking  */
        if( (x_pos_file == NULL) ||
                (x_neg_file == NULL) ||
                (y_pos_file == NULL) ||
                (y_neg_file == NULL) ||
                (z_pos_file == NULL) ||
                (z_neg_file == NULL) )
        {
                result_string_pointer = "Invalid cube map files list";
                return 0;
        }
        /*      capability checking     */
        if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
        {
                result_string_pointer = "No cube map capability present";
                return 0;
        }
        /*      1st face: try to load the image */
        img = SOIL_load_image( x_pos_file, &width, &height, &channels, force_channels );
        /*      channels holds the original number of channels, which may have been forced      */
        if( (force_channels >= 1) && (force_channels <= 4) )
        {
                channels = force_channels;
        }
        if( NULL == img )
        {
                /*      image loading failed    */
                result_string_pointer = stbi_failure_reason();
                return 0;
        }
        /*      upload the texture, and create a texture ID if necessary        */
        tex_id = SOIL_internal_create_OGL_texture(
                        img, width, height, channels,
                        reuse_texture_ID, flags,
                        SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X,
                        SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
        /*      and nuke the image data */
        SOIL_free_image_data( img );
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image( x_neg_file, &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image( y_pos_file, &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image( y_neg_file, &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image( z_pos_file, &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image( z_neg_file, &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      and return the handle, such as it is    */
        return tex_id;
}

unsigned int
        SOIL_load_OGL_cubemap_from_memory
        (
                const unsigned char *const x_pos_buffer,
                int x_pos_buffer_length,
                const unsigned char *const x_neg_buffer,
                int x_neg_buffer_length,
                const unsigned char *const y_pos_buffer,
                int y_pos_buffer_length,
                const unsigned char *const y_neg_buffer,
                int y_neg_buffer_length,
                const unsigned char *const z_pos_buffer,
                int z_pos_buffer_length,
                const unsigned char *const z_neg_buffer,
                int z_neg_buffer_length,
                int force_channels,
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      variables       */
        unsigned char* img;
        int width, height, channels;
        unsigned int tex_id;
        /*      error checking  */
        if( (x_pos_buffer == NULL) ||
                (x_neg_buffer == NULL) ||
                (y_pos_buffer == NULL) ||
                (y_neg_buffer == NULL) ||
                (z_pos_buffer == NULL) ||
                (z_neg_buffer == NULL) )
        {
                result_string_pointer = "Invalid cube map buffers list";
                return 0;
        }
        /*      capability checking     */
        if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
        {
                result_string_pointer = "No cube map capability present";
                return 0;
        }
        /*      1st face: try to load the image */
        img = SOIL_load_image_from_memory(
                        x_pos_buffer, x_pos_buffer_length,
                        &width, &height, &channels, force_channels );
        /*      channels holds the original number of channels, which may have been forced      */
        if( (force_channels >= 1) && (force_channels <= 4) )
        {
                channels = force_channels;
        }
        if( NULL == img )
        {
                /*      image loading failed    */
                result_string_pointer = stbi_failure_reason();
                return 0;
        }
        /*      upload the texture, and create a texture ID if necessary        */
        tex_id = SOIL_internal_create_OGL_texture(
                        img, width, height, channels,
                        reuse_texture_ID, flags,
                        SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_X,
                        SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
        /*      and nuke the image data */
        SOIL_free_image_data( img );
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image_from_memory(
                                x_neg_buffer, x_neg_buffer_length,
                                &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image_from_memory(
                                y_pos_buffer, y_pos_buffer_length,
                                &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image_from_memory(
                                y_neg_buffer, y_neg_buffer_length,
                                &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image_from_memory(
                                z_pos_buffer, z_pos_buffer_length,
                                &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      continue?       */
        if( tex_id != 0 )
        {
                /*      1st face: try to load the image */
                img = SOIL_load_image_from_memory(
                                z_neg_buffer, z_neg_buffer_length,
                                &width, &height, &channels, force_channels );
                /*      channels holds the original number of channels, which may have been forced      */
                if( (force_channels >= 1) && (force_channels <= 4) )
                {
                        channels = force_channels;
                }
                if( NULL == img )
                {
                        /*      image loading failed    */
                        result_string_pointer = stbi_failure_reason();
                        return 0;
                }
                /*      upload the texture, but reuse the assigned texture ID   */
                tex_id = SOIL_internal_create_OGL_texture(
                                img, width, height, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP, SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
                /*      and nuke the image data */
                SOIL_free_image_data( img );
        }
        /*      and return the handle, such as it is    */
        return tex_id;
}

unsigned int
        SOIL_load_OGL_single_cubemap
        (
                const char *filename,
                const char face_order[6],
                int force_channels,
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      variables       */
        unsigned char* img;
        int width, height, channels, i;
        unsigned int tex_id = 0;
        /*      error checking  */
        if( filename == NULL )
        {
                result_string_pointer = "Invalid single cube map file name";
                return 0;
        }
        /*      does the user want direct uploading of the image as a DDS file? */
        if( flags & SOIL_FLAG_DDS_LOAD_DIRECT )
        {
                /*      1st try direct loading of the image as a DDS file
                        note: direct uploading will only load what is in the
                        DDS file, no MIPmaps will be generated, the image will
                        not be flipped, etc.    */
                tex_id = SOIL_direct_load_DDS( filename, reuse_texture_ID, flags, 1 );
                if( tex_id )
                {
                        /*      hey, it worked!!        */
                        return tex_id;
                }
        }
        /*      face order checking     */
        for( i = 0; i < 6; ++i )
        {
                if( (face_order[i] != 'N') &&
                        (face_order[i] != 'S') &&
                        (face_order[i] != 'W') &&
                        (face_order[i] != 'E') &&
                        (face_order[i] != 'U') &&
                        (face_order[i] != 'D') )
                {
                        result_string_pointer = "Invalid single cube map face order";
                        return 0;
                };
        }
        /*      capability checking     */
        if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
        {
                result_string_pointer = "No cube map capability present";
                return 0;
        }
        /*      1st off, try to load the full image     */
        img = SOIL_load_image( filename, &width, &height, &channels, force_channels );
        /*      channels holds the original number of channels, which may have been forced      */
        if( (force_channels >= 1) && (force_channels <= 4) )
        {
                channels = force_channels;
        }
        if( NULL == img )
        {
                /*      image loading failed    */
                result_string_pointer = stbi_failure_reason();
                return 0;
        }
        /*      now, does this image have the right dimensions? */
        if( (width != 6*height) &&
                (6*width != height) )
        {
                SOIL_free_image_data( img );
                result_string_pointer = "Single cubemap image must have a 6:1 ratio";
                return 0;
        }
        /*      try the image split and create  */
        tex_id = SOIL_create_OGL_single_cubemap(
                        img, width, height, channels,
                        face_order, reuse_texture_ID, flags
                        );
        /*      nuke the temporary image data and return the texture handle     */
        SOIL_free_image_data( img );
        return tex_id;
}

unsigned int
        SOIL_load_OGL_single_cubemap_from_memory
        (
                const unsigned char *const buffer,
                int buffer_length,
                const char face_order[6],
                int force_channels,
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      variables       */
        unsigned char* img;
        int width, height, channels, i;
        unsigned int tex_id = 0;
        /*      error checking  */
        if( buffer == NULL )
        {
                result_string_pointer = "Invalid single cube map buffer";
                return 0;
        }
        /*      does the user want direct uploading of the image as a DDS file? */
        if( flags & SOIL_FLAG_DDS_LOAD_DIRECT )
        {
                /*      1st try direct loading of the image as a DDS file
                        note: direct uploading will only load what is in the
                        DDS file, no MIPmaps will be generated, the image will
                        not be flipped, etc.    */
                tex_id = SOIL_direct_load_DDS_from_memory(
                                buffer, buffer_length,
                                reuse_texture_ID, flags, 1 );
                if( tex_id )
                {
                        /*      hey, it worked!!        */
                        return tex_id;
                }
        }
        /*      face order checking     */
        for( i = 0; i < 6; ++i )
        {
                if( (face_order[i] != 'N') &&
                        (face_order[i] != 'S') &&
                        (face_order[i] != 'W') &&
                        (face_order[i] != 'E') &&
                        (face_order[i] != 'U') &&
                        (face_order[i] != 'D') )
                {
                        result_string_pointer = "Invalid single cube map face order";
                        return 0;
                };
        }
        /*      capability checking     */
        if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
        {
                result_string_pointer = "No cube map capability present";
                return 0;
        }
        /*      1st off, try to load the full image     */
        img = SOIL_load_image_from_memory(
                        buffer, buffer_length,
                        &width, &height, &channels,
                        force_channels );
        /*      channels holds the original number of channels, which may have been forced      */
        if( (force_channels >= 1) && (force_channels <= 4) )
        {
                channels = force_channels;
        }
        if( NULL == img )
        {
                /*      image loading failed    */
                result_string_pointer = stbi_failure_reason();
                return 0;
        }
        /*      now, does this image have the right dimensions? */
        if( (width != 6*height) &&
                (6*width != height) )
        {
                SOIL_free_image_data( img );
                result_string_pointer = "Single cubemap image must have a 6:1 ratio";
                return 0;
        }
        /*      try the image split and create  */
        tex_id = SOIL_create_OGL_single_cubemap(
                        img, width, height, channels,
                        face_order, reuse_texture_ID, flags
                        );
        /*      nuke the temporary image data and return the texture handle     */
        SOIL_free_image_data( img );
        return tex_id;
}

unsigned int
        SOIL_create_OGL_single_cubemap
        (
                const unsigned char *const data,
                int width, int height, int channels,
                const char face_order[6],
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      variables       */
        unsigned char* sub_img;
        int dw, dh, sz, i;
        unsigned int tex_id;
        /*      error checking  */
        if( data == NULL )
        {
                result_string_pointer = "Invalid single cube map image data";
                return 0;
        }
        /*      face order checking     */
        for( i = 0; i < 6; ++i )
        {
                if( (face_order[i] != 'N') &&
                        (face_order[i] != 'S') &&
                        (face_order[i] != 'W') &&
                        (face_order[i] != 'E') &&
                        (face_order[i] != 'U') &&
                        (face_order[i] != 'D') )
                {
                        result_string_pointer = "Invalid single cube map face order";
                        return 0;
                };
        }
        /*      capability checking     */
        if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
        {
                result_string_pointer = "No cube map capability present";
                return 0;
        }
        /*      now, does this image have the right dimensions? */
        if( (width != 6*height) &&
                (6*width != height) )
        {
                result_string_pointer = "Single cubemap image must have a 6:1 ratio";
                return 0;
        }
        /*      which way am I stepping?        */
        if( width > height )
        {
                dw = height;
                dh = 0;
        } else
        {
                dw = 0;
                dh = width;
        }
        sz = dw+dh;
        sub_img = (unsigned char *)malloc( sz*sz*channels );
        /*      do the splitting and uploading  */
        tex_id = reuse_texture_ID;
        for( i = 0; i < 6; ++i )
        {
                int x, y, idx = 0;
                unsigned int cubemap_target = 0;
                /*      copy in the sub-image   */
                for( y = i*dh; y < i*dh+sz; ++y )
                {
                        for( x = i*dw*channels; x < (i*dw+sz)*channels; ++x )
                        {
                                sub_img[idx++] = data[y*width*channels+x];
                        }
                }
                /*      what is my texture target?
                        remember, this coordinate system is
                        LHS if viewed from inside the cube!     */
                switch( face_order[i] )
                {
                case 'N':
                        cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_Z;
                        break;
                case 'S':
                        cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
                        break;
                case 'W':
                        cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_X;
                        break;
                case 'E':
                        cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_X;
                        break;
                case 'U':
                        cubemap_target = SOIL_TEXTURE_CUBE_MAP_POSITIVE_Y;
                        break;
                case 'D':
                        cubemap_target = SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
                        break;
                }
                /*      upload it as a texture  */
                tex_id = SOIL_internal_create_OGL_texture(
                                sub_img, sz, sz, channels,
                                tex_id, flags,
                                SOIL_TEXTURE_CUBE_MAP,
                                cubemap_target,
                                SOIL_MAX_CUBE_MAP_TEXTURE_SIZE );
        }
        /*      and nuke the image and sub-image data   */
        SOIL_free_image_data( sub_img );
        /*      and return the handle, such as it is    */
        return tex_id;
}

unsigned int
        SOIL_create_OGL_texture
        (
                const unsigned char *const data,
                int width, int height, int channels,
                unsigned int reuse_texture_ID,
                unsigned int flags
        )
{
        /*      wrapper function for 2D textures        */
        return SOIL_internal_create_OGL_texture(
                                data, width, height, channels,
                                reuse_texture_ID, flags,
                                GL_TEXTURE_2D, GL_TEXTURE_2D,
                                GL_MAX_TEXTURE_SIZE );
}

#if SOIL_CHECK_FOR_GL_ERRORS
void check_for_GL_errors( const char *calling_location )
{
        /*      check for errors        */
        GLenum err_code = glGetError();
        while( GL_NO_ERROR != err_code )
        {
                printf( "OpenGL Error @ %s: %i", calling_location, err_code );
                err_code = glGetError();
        }
}
#else

void check_for_GL_errors( const char *calling_location )
{
        /*      no check for errors     */
}
#endif


unsigned int
        SOIL_internal_create_OGL_texture
        (
                const unsigned char *const data,
                int width, int height, int channels,
                unsigned int reuse_texture_ID,
                unsigned int flags,
                unsigned int opengl_texture_type,
                unsigned int opengl_texture_target,
                unsigned int texture_check_size_enum
        )
{
        /*      variables       */
        unsigned char* img;
        unsigned int tex_id;
        unsigned int internal_texture_format = 0, original_texture_format = 0;
        int DXT_mode = SOIL_CAPABILITY_UNKNOWN;
        int max_supported_size;
        /*      If the user wants to use the texture rectangle I kill a few flags       */
        if( flags & SOIL_FLAG_TEXTURE_RECTANGLE )
        {
                /*      well, the user asked for it, can we do that?    */
                if( query_tex_rectangle_capability() == SOIL_CAPABILITY_PRESENT )
                {
                        /*      only allow this if the user in _NOT_ trying to do a cubemap!    */
                        if( opengl_texture_type == GL_TEXTURE_2D )
                        {
                                /*      clean out the flags that cannot be used with texture rectangles */
                                flags &= ~(
                                                SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS |
                                                SOIL_FLAG_TEXTURE_REPEATS
                                        );
                                /*      and change my target    */
                                opengl_texture_target = SOIL_TEXTURE_RECTANGLE_ARB;
                                opengl_texture_type = SOIL_TEXTURE_RECTANGLE_ARB;
                        } else
                        {
                                /*      not allowed for any other uses (yes, I'm looking at you, cubemaps!)     */
                                flags &= ~SOIL_FLAG_TEXTURE_RECTANGLE;
                        }

                } else
                {
                        /*      can't do it, and that is a breakable offense (uv coords use pixels instead of [0,1]!)   */
                        result_string_pointer = "Texture Rectangle extension unsupported";
                        return 0;
                }
        }
        /*      create a copy the image data    */
        img = (unsigned char*)malloc( width*height*channels );
        memcpy( img, data, width*height*channels );
        /*      does the user want me to invert the image?      */
        if( flags & SOIL_FLAG_INVERT_Y )
        {
                int i, j;
                for( j = 0; j*2 < height; ++j )
                {
                        int index1 = j * width * channels;
                        int index2 = (height - 1 - j) * width * channels;
                        for( i = width * channels; i > 0; --i )
                        {
                                unsigned char temp = img[index1];
                                img[index1] = img[index2];
                                img[index2] = temp;
                                ++index1;
                                ++index2;
                        }
                }
        }
        /*      does the user want me to scale the colors into the NTSC safe RGB range? */
        if( flags & SOIL_FLAG_NTSC_SAFE_RGB )
        {
                scale_image_RGB_to_NTSC_safe( img, width, height, channels );
        }
        /*      does the user want me to convert from straight to pre-multiplied alpha?
                (and do we even _have_ alpha?)  */
        if( flags & SOIL_FLAG_MULTIPLY_ALPHA )
        {
                int i;
                switch( channels )
                {
                case 2:
                        for( i = 0; i < 2*width*height; i += 2 )
                        {
                                img[i] = (img[i] * img[i+1] + 128) >> 8;
                        }
                        break;
                case 4:
                        for( i = 0; i < 4*width*height; i += 4 )
                        {
                                img[i+0] = (img[i+0] * img[i+3] + 128) >> 8;
                                img[i+1] = (img[i+1] * img[i+3] + 128) >> 8;
                                img[i+2] = (img[i+2] * img[i+3] + 128) >> 8;
                        }
                        break;
                default:
                        /*      no other number of channels contains alpha data */
                        break;
                }
        }
        /*      if the user can't support NPOT textures, make sure we force the POT option      */
        if( (query_NPOT_capability() == SOIL_CAPABILITY_NONE) &&
                !(flags & SOIL_FLAG_TEXTURE_RECTANGLE) )
        {
                /*      add in the POT flag */
                flags |= SOIL_FLAG_POWER_OF_TWO;
        }
        /*      how large of a texture can this OpenGL implementation handle?   */
        /*      texture_check_size_enum will be GL_MAX_TEXTURE_SIZE or SOIL_MAX_CUBE_MAP_TEXTURE_SIZE   */
        glGetIntegerv( texture_check_size_enum, &max_supported_size );
        /*      do I need to make it a power of 2?      */
        if(
                (flags & SOIL_FLAG_POWER_OF_TWO) ||     /*      user asked for it       */
                (flags & SOIL_FLAG_MIPMAPS) ||          /*      need it for the MIP-maps        */
                (width > max_supported_size) ||         /*      it's too big, (make sure it's   */
                (height > max_supported_size) )         /*      2^n for later down-sampling)    */
        {
                int new_width = 1;
                int new_height = 1;
                while( new_width < width )
                {
                        new_width *= 2;
                }
                while( new_height < height )
                {
                        new_height *= 2;
                }
                /*      still?  */
                if( (new_width != width) || (new_height != height) )
                {
                        /*      yep, resize     */
                        unsigned char *resampled = (unsigned char*)malloc( channels*new_width*new_height );
                        up_scale_image(
                                        img, width, height, channels,
                                        resampled, new_width, new_height );
                        /*      OJO     this is for debug only! */
                        /*
                        SOIL_save_image( "\\showme.bmp", SOIL_SAVE_TYPE_BMP,
                                                        new_width, new_height, channels,
                                                        resampled );
                        */
                        /*      nuke the old guy, then point it at the new guy  */
                        SOIL_free_image_data( img );
                        img = resampled;
                        width = new_width;
                        height = new_height;
                }
        }
        /*      now, if it is too large...      */
        if( (width > max_supported_size) || (height > max_supported_size) )
        {
                /*      I've already made it a power of two, so simply use the MIPmapping
                        code to reduce its size to the allowable maximum.       */
                unsigned char *resampled;
                int reduce_block_x = 1, reduce_block_y = 1;
                int new_width, new_height;
                if( width > max_supported_size )
                {
                        reduce_block_x = width / max_supported_size;
                }
                if( height > max_supported_size )
                {
                        reduce_block_y = height / max_supported_size;
                }
                new_width = width / reduce_block_x;
                new_height = height / reduce_block_y;
                resampled = (unsigned char*)malloc( channels*new_width*new_height );
                /*      perform the actual reduction    */
                mipmap_image(   img, width, height, channels,
                                                resampled, reduce_block_x, reduce_block_y );
                /*      nuke the old guy, then point it at the new guy  */
                SOIL_free_image_data( img );
                img = resampled;
                width = new_width;
                height = new_height;
        }
        /*      does the user want us to use YCoCg color space? */
        if( flags & SOIL_FLAG_CoCg_Y )
        {
                /*      this will only work with RGB and RGBA images */
                convert_RGB_to_YCoCg( img, width, height, channels );
                /*
                save_image_as_DDS( "CoCg_Y.dds", width, height, channels, img );
                */
        }
        /*      create the OpenGL texture ID handle
        (note: allowing a forced texture ID lets me reload a texture)   */
    tex_id = reuse_texture_ID;
    if( tex_id == 0 )
    {
                glGenTextures( 1, &tex_id );
    }
        check_for_GL_errors( "glGenTextures" );
        /* Note: sometimes glGenTextures fails (usually no OpenGL context)      */
        if( tex_id )
        {
                /*      and what type am I using as the internal texture format?        */
                switch( channels )
                {
                case 1:
                        original_texture_format = GL_LUMINANCE;
                        break;
                case 2:
                        original_texture_format = GL_LUMINANCE_ALPHA;
                        break;
                case 3:
                        original_texture_format = GL_RGB;
                        break;
                case 4:
                        original_texture_format = GL_RGBA;
                        break;
                }
                internal_texture_format = original_texture_format;
                /*      does the user want me to, and can I, save as DXT?       */
                if( flags & SOIL_FLAG_COMPRESS_TO_DXT )
                {
                        DXT_mode = query_DXT_capability();
                        if( DXT_mode == SOIL_CAPABILITY_PRESENT )
                        {
                                /*      I can use DXT, whether I compress it or OpenGL does     */
                                if( (channels & 1) == 1 )
                                {
                                        /*      1 or 3 channels = DXT1  */
                                        internal_texture_format = SOIL_RGB_S3TC_DXT1;
                                } else
                                {
                                        /*      2 or 4 channels = DXT5  */
                                        internal_texture_format = SOIL_RGBA_S3TC_DXT5;
                                }
                        }
                }
                /*  bind an OpenGL texture ID   */
                glBindTexture( opengl_texture_type, tex_id );
                check_for_GL_errors( "glBindTexture" );
                /*  upload the main image       */
                if( DXT_mode == SOIL_CAPABILITY_PRESENT )
                {
                        /*      user wants me to do the DXT conversion! */
                        int DDS_size;
                        unsigned char *DDS_data = NULL;
                        if( (channels & 1) == 1 )
                        {
                                /*      RGB, use DXT1   */
                                DDS_data = convert_image_to_DXT1( img, width, height, channels, &DDS_size );
                        } else
                        {
                                /*      RGBA, use DXT5  */
                                DDS_data = convert_image_to_DXT5( img, width, height, channels, &DDS_size );
                        }
                        if( DDS_data )
                        {
                                soilGlCompressedTexImage2D(
                                        opengl_texture_target, 0,
                                        internal_texture_format, width, height, 0,
                                        DDS_size, DDS_data );
                                check_for_GL_errors( "glCompressedTexImage2D" );
                                SOIL_free_image_data( DDS_data );
                                /*      printf( "Internal DXT compressor\n" );  */
                        } else
                        {
                                /*      my compression failed, try the OpenGL driver's version  */
                                glTexImage2D(
                                        opengl_texture_target, 0,
                                        internal_texture_format, width, height, 0,
                                        original_texture_format, GL_UNSIGNED_BYTE, img );
                                check_for_GL_errors( "glTexImage2D" );
                                /*      printf( "OpenGL DXT compressor\n" );    */
                        }
                } else
                {
                        /*      user want OpenGL to do all the work!    */
                        glTexImage2D(
                                opengl_texture_target, 0,
                                internal_texture_format, width, height, 0,
                                original_texture_format, GL_UNSIGNED_BYTE, img );
                        check_for_GL_errors( "glTexImage2D" );
                        /*printf( "OpenGL DXT compressor\n" );  */
                }
                /*      are any MIPmaps desired?        */
                if( flags & SOIL_FLAG_MIPMAPS )
                {
                        int MIPlevel = 1;
                        int MIPwidth = (width+1) / 2;
                        int MIPheight = (height+1) / 2;
                        unsigned char *resampled = (unsigned char*)malloc( channels*MIPwidth*MIPheight );
                        while( ((1<<MIPlevel) <= width) || ((1<<MIPlevel) <= height) )
                        {
                                /*      do this MIPmap level    */
                                mipmap_image(
                                                img, width, height, channels,
                                                resampled,
                                                (1 << MIPlevel), (1 << MIPlevel) );
                                /*  upload the MIPmaps  */
                                if( DXT_mode == SOIL_CAPABILITY_PRESENT )
                                {
                                        /*      user wants me to do the DXT conversion! */
                                        int DDS_size;
                                        unsigned char *DDS_data = NULL;
                                        if( (channels & 1) == 1 )
                                        {
                                                /*      RGB, use DXT1   */
                                                DDS_data = convert_image_to_DXT1(
                                                                resampled, MIPwidth, MIPheight, channels, &DDS_size );
                                        } else
                                        {
                                                /*      RGBA, use DXT5  */
                                                DDS_data = convert_image_to_DXT5(
                                                                resampled, MIPwidth, MIPheight, channels, &DDS_size );
                                        }
                                        if( DDS_data )
                                        {
                                                soilGlCompressedTexImage2D(
                                                        opengl_texture_target, MIPlevel,
                                                        internal_texture_format, MIPwidth, MIPheight, 0,
                                                        DDS_size, DDS_data );
                                                check_for_GL_errors( "glCompressedTexImage2D" );
                                                SOIL_free_image_data( DDS_data );
                                        } else
                                        {
                                                /*      my compression failed, try the OpenGL driver's version  */
                                                glTexImage2D(
                                                        opengl_texture_target, MIPlevel,
                                                        internal_texture_format, MIPwidth, MIPheight, 0,
                                                        original_texture_format, GL_UNSIGNED_BYTE, resampled );
                                                check_for_GL_errors( "glTexImage2D" );
                                        }
                                } else
                                {
                                        /*      user want OpenGL to do all the work!    */
                                        glTexImage2D(
                                                opengl_texture_target, MIPlevel,
                                                internal_texture_format, MIPwidth, MIPheight, 0,
                                                original_texture_format, GL_UNSIGNED_BYTE, resampled );
                                        check_for_GL_errors( "glTexImage2D" );
                                }
                                /*      prep for the next level */
                                ++MIPlevel;
                                MIPwidth = (MIPwidth + 1) / 2;
                                MIPheight = (MIPheight + 1) / 2;
                        }
                        SOIL_free_image_data( resampled );
                        /*      instruct OpenGL to use the MIPmaps      */
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
                        check_for_GL_errors( "GL_TEXTURE_MIN/MAG_FILTER" );
                } else
                {
                        /*      instruct OpenGL _NOT_ to use the MIPmaps        */
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
                        check_for_GL_errors( "GL_TEXTURE_MIN/MAG_FILTER" );
                }
                /*      does the user want clamping, or wrapping?       */
                if( flags & SOIL_FLAG_TEXTURE_REPEATS )
                {
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT );
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT );
                        if( opengl_texture_type == SOIL_TEXTURE_CUBE_MAP )
                        {
                                /*      SOIL_TEXTURE_WRAP_R is invalid if cubemaps aren't supported     */
                                glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT );
                        }
                        check_for_GL_errors( "GL_TEXTURE_WRAP_*" );
                } else
                {
                        /*      unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE;   */
                        unsigned int clamp_mode = GL_CLAMP;
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode );
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode );
                        if( opengl_texture_type == SOIL_TEXTURE_CUBE_MAP )
                        {
                                /*      SOIL_TEXTURE_WRAP_R is invalid if cubemaps aren't supported     */
                                glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode );
                        }
                        check_for_GL_errors( "GL_TEXTURE_WRAP_*" );
                }
                /*      done    */
                result_string_pointer = "Image loaded as an OpenGL texture";
        } else
        {
                /*      failed  */
                result_string_pointer = "Failed to generate an OpenGL texture name; missing OpenGL context?";
        }
        SOIL_free_image_data( img );
        return tex_id;
}

int
        SOIL_save_screenshot
        (
                const char *filename,
                int image_type,
                int x, int y,
                int width, int height
        )
{
        unsigned char *pixel_data;
        int i, j;
        int save_result;

        /*      error checks    */
        if( (width < 1) || (height < 1) )
        {
                result_string_pointer = "Invalid screenshot dimensions";
                return 0;
        }
        if( (x < 0) || (y < 0) )
        {
                result_string_pointer = "Invalid screenshot location";
                return 0;
        }
        if( filename == NULL )
        {
                result_string_pointer = "Invalid screenshot filename";
                return 0;
        }
    glPixelStorei(GL_PACK_ALIGNMENT, 1); // allow images where width is multiple of 1 (default is 4)
    /*  Get the data from OpenGL        */
    pixel_data = (unsigned char*)malloc( 3*width*height );
    glReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixel_data);

    /*  invert the image        */
    for( j = 0; j*2 < height; ++j )
        {
                int index1 = j * width * 3;
                int index2 = (height - 1 - j) * width * 3;
                for( i = width * 3; i > 0; --i )
                {
                        unsigned char temp = pixel_data[index1];
                        pixel_data[index1] = pixel_data[index2];
                        pixel_data[index2] = temp;
                        ++index1;
                        ++index2;
                }
        }

    /*  save the image  */
    save_result = SOIL_save_image( filename, image_type, width, height, 3, pixel_data);

    /*  And free the memory     */
    SOIL_free_image_data( pixel_data );
        return save_result;
}

unsigned char*
        SOIL_load_image
        (
                const char *filename,
                int *width, int *height, int *channels,
                int force_channels
        )
{
        unsigned char *result = stbi_load( filename,
                        width, height, channels, force_channels );
        if( result == NULL )
        {
                result_string_pointer = stbi_failure_reason();
        } else
        {
                result_string_pointer = "Image loaded";
        }
        return result;
}

unsigned char*
        SOIL_load_HDR_image
        (
                const char *filename,
                int *width, int *height, int *channels,
                int force_channels
        )
{
        unsigned char *result = stbi_hdr_load_rgbe( filename,
                        width, height, channels, force_channels );
        if( result == NULL )
        {
                result_string_pointer = stbi_failure_reason();
        } else
        {
                result_string_pointer = "Image loaded";
        }
        return result;
}

unsigned char*
        SOIL_load_image_from_memory
        (
                const unsigned char *const buffer,
                int buffer_length,
                int *width, int *height, int *channels,
                int force_channels
        )
{
        unsigned char *result = stbi_load_from_memory(
                                buffer, buffer_length,
                                width, height, channels,
                                force_channels );
        if( result == NULL )
        {
                result_string_pointer = stbi_failure_reason();
        } else
        {
                result_string_pointer = "Image loaded from memory";
        }
        return result;
}

int
        SOIL_save_image
        (
                const char *filename,
                int image_type,
                int width, int height, int channels,
                const unsigned char *const data
        )
{
        int save_result;

        /*      error check     */
        if( (width < 1) || (height < 1) ||
                (channels < 1) || (channels > 4) ||
                (data == NULL) ||
                (filename == NULL) )
        {
                return 0;
        }
        if( image_type == SOIL_SAVE_TYPE_BMP )
        {
                save_result = stbi_write_bmp( filename,
                                width, height, channels, (void*)data );
        } else
        if( image_type == SOIL_SAVE_TYPE_TGA )
        {
                save_result = stbi_write_tga( filename,
                                width, height, channels, (void*)data );
        } else
        if( image_type == SOIL_SAVE_TYPE_DDS )
        {
                save_result = save_image_as_DDS( filename,
                                width, height, channels, (const unsigned char *const)data );
        } else
        {
                save_result = 0;
        }
        if( save_result == 0 )
        {
                result_string_pointer = "Saving the image failed";
        } else
        {
                result_string_pointer = "Image saved";
        }
        return save_result;
}

void
        SOIL_free_image_data
        (
                unsigned char *img_data
        )
{
        free( (void*)img_data );
}

const char*
        SOIL_last_result
        (
                void
        )
{
        return result_string_pointer;
}

unsigned int SOIL_direct_load_DDS_from_memory(
                const unsigned char *const buffer,
                int buffer_length,
                unsigned int reuse_texture_ID,
                int flags,
                int loading_as_cubemap )
{
        /*      variables       */
        DDS_header header;
        unsigned int buffer_index = 0;
        unsigned int tex_ID = 0;
        /*      file reading variables  */
        unsigned int S3TC_type = 0;
        unsigned char *DDS_data;
        unsigned int DDS_main_size;
        unsigned int DDS_full_size;
        unsigned int width, height;
        int mipmaps, cubemap, uncompressed, block_size = 16;
        unsigned int flag;
        unsigned int cf_target, ogl_target_start, ogl_target_end;
        unsigned int opengl_texture_type;
        int i;
        /*      1st off, does the filename even exist?  */
        if( NULL == buffer )
        {
                /*      we can't do it! */
                result_string_pointer = "NULL buffer";
                return 0;
        }
        if( buffer_length < sizeof( DDS_header ) )
        {
                /*      we can't do it! */
                result_string_pointer = "DDS file was too small to contain the DDS header";
                return 0;
        }
        /*      try reading in the header       */
        memcpy ( (void*)(&header), (const void *)buffer, sizeof( DDS_header ) );
        buffer_index = sizeof( DDS_header );
        /*      guilty until proven innocent    */
        result_string_pointer = "Failed to read a known DDS header";
        /*      validate the header (warning, "goto"'s ahead, shield your eyes!!)       */
        flag = ('D'<<0)|('D'<<8)|('S'<<16)|(' '<<24);
        if( header.dwMagic != flag ) {goto quick_exit;}
        if( header.dwSize != 124 ) {goto quick_exit;}
        /*      I need all of these     */
        flag = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
        if( (header.dwFlags & flag) != flag ) {goto quick_exit;}
        /*      According to the MSDN spec, the dwFlags should contain
                DDSD_LINEARSIZE if it's compressed, or DDSD_PITCH if
                uncompressed.  Some DDS writers do not conform to the
                spec, so I need to make my reader more tolerant */
        /*      I need one of these     */
        flag = DDPF_FOURCC | DDPF_RGB;
        if( (header.sPixelFormat.dwFlags & flag) == 0 ) {goto quick_exit;}
        if( header.sPixelFormat.dwSize != 32 ) {goto quick_exit;}
        if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) {goto quick_exit;}
        /*      make sure it is a type we can upload    */
        if( (header.sPixelFormat.dwFlags & DDPF_FOURCC) &&
                !(
                (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24))) ||
                (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('3'<<24))) ||
                (header.sPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('5'<<24)))
                ) )
        {
                goto quick_exit;
        }
        /*      OK, validated the header, let's load the image data     */
        result_string_pointer = "DDS header loaded and validated";
        width = header.dwWidth;
        height = header.dwHeight;
        uncompressed = 1 - (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC;
        cubemap = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP;
        if( uncompressed )
        {
                S3TC_type = GL_RGB;
                block_size = 3;
                if( header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS )
                {
                        S3TC_type = GL_RGBA;
                        block_size = 4;
                }
                DDS_main_size = width * height * block_size;
        } else
        {
                /*      can we even handle direct uploading to OpenGL DXT compressed images?    */
                if( query_DXT_capability() != SOIL_CAPABILITY_PRESENT )
                {
                        /*      we can't do it! */
                        result_string_pointer = "Direct upload of S3TC images not supported by the OpenGL driver";
                        return 0;
                }
                /*      well, we know it is DXT1/3/5, because we checked above  */
                switch( (header.sPixelFormat.dwFourCC >> 24) - '0' )
                {
                case 1:
                        S3TC_type = SOIL_RGBA_S3TC_DXT1;
                        block_size = 8;
                        break;
                case 3:
                        S3TC_type = SOIL_RGBA_S3TC_DXT3;
                        block_size = 16;
                        break;
                case 5:
                        S3TC_type = SOIL_RGBA_S3TC_DXT5;
                        block_size = 16;
                        break;
                }
                DDS_main_size = ((width+3)>>2)*((height+3)>>2)*block_size;
        }
        if( cubemap )
        {
                /* does the user want a cubemap?        */
                if( !loading_as_cubemap )
                {
                        /*      we can't do it! */
                        result_string_pointer = "DDS image was a cubemap";
                        return 0;
                }
                /*      can we even handle cubemaps with the OpenGL driver?     */
                if( query_cubemap_capability() != SOIL_CAPABILITY_PRESENT )
                {
                        /*      we can't do it! */
                        result_string_pointer = "Direct upload of cubemap images not supported by the OpenGL driver";
                        return 0;
                }
                ogl_target_start = SOIL_TEXTURE_CUBE_MAP_POSITIVE_X;
                ogl_target_end =   SOIL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
                opengl_texture_type = SOIL_TEXTURE_CUBE_MAP;
        } else
        {
                /* does the user want a non-cubemap?    */
                if( loading_as_cubemap )
                {
                        /*      we can't do it! */
                        result_string_pointer = "DDS image was not a cubemap";
                        return 0;
                }
                ogl_target_start = GL_TEXTURE_2D;
                ogl_target_end =   GL_TEXTURE_2D;
                opengl_texture_type = GL_TEXTURE_2D;
        }
        if( (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1) )
        {
                int shift_offset;
                mipmaps = header.dwMipMapCount - 1;
                DDS_full_size = DDS_main_size;
                if( uncompressed )
                {
                        /*      uncompressed DDS, simple MIPmap size calculation        */
                        shift_offset = 0;
                } else
                {
                        /*      compressed DDS, MIPmap size calculation is block based  */
                        shift_offset = 2;
                }
                for( i = 1; i <= mipmaps; ++ i )
                {
                        int w, h;
                        w = width >> (shift_offset + i);
                        h = height >> (shift_offset + i);
                        if( w < 1 )
                        {
                                w = 1;
                        }
                        if( h < 1 )
                        {
                                h = 1;
                        }
                        DDS_full_size += w*h*block_size;
                }
        } else
        {
                mipmaps = 0;
                DDS_full_size = DDS_main_size;
        }
        DDS_data = (unsigned char*)malloc( DDS_full_size );
        /*      got the image data RAM, create or use an existing OpenGL texture handle */
        tex_ID = reuse_texture_ID;
        if( tex_ID == 0 )
        {
                glGenTextures( 1, &tex_ID );
        }
        /*  bind an OpenGL texture ID   */
        glBindTexture( opengl_texture_type, tex_ID );
        /*      do this for each face of the cubemap!   */
        for( cf_target = ogl_target_start; cf_target <= ogl_target_end; ++cf_target )
        {
                if( buffer_index + DDS_full_size <= (unsigned int)buffer_length )
                {
                        unsigned int byte_offset = DDS_main_size;
                        memcpy( (void*)DDS_data, (const void*)(&buffer[buffer_index]), DDS_full_size );
                        buffer_index += DDS_full_size;
                        /*      upload the main chunk   */
                        if( uncompressed )
                        {
                                /*      and remember, DXT uncompressed uses BGR(A),
                                        so swap to RGB(A) for ALL MIPmap levels */
                                for( i = 0; i < (int)DDS_full_size; i += block_size )
                                {
                                        unsigned char temp = DDS_data[i];
                                        DDS_data[i] = DDS_data[i+2];
                                        DDS_data[i+2] = temp;
                                }
                                glTexImage2D(
                                        cf_target, 0,
                                        S3TC_type, width, height, 0,
                                        S3TC_type, GL_UNSIGNED_BYTE, DDS_data );
                        } else
                        {
                                soilGlCompressedTexImage2D(
                                        cf_target, 0,
                                        S3TC_type, width, height, 0,
                                        DDS_main_size, DDS_data );
                        }
                        /*      upload the mipmaps, if we have them     */
                        for( i = 1; i <= mipmaps; ++i )
                        {
                                int w, h, mip_size;
                                w = width >> i;
                                h = height >> i;
                                if( w < 1 )
                                {
                                        w = 1;
                                }
                                if( h < 1 )
                                {
                                        h = 1;
                                }
                                /*      upload this mipmap      */
                                if( uncompressed )
                                {
                                        mip_size = w*h*block_size;
                                        glTexImage2D(
                                                cf_target, i,
                                                S3TC_type, w, h, 0,
                                                S3TC_type, GL_UNSIGNED_BYTE, &DDS_data[byte_offset] );
                                } else
                                {
                                        mip_size = ((w+3)/4)*((h+3)/4)*block_size;
                                        soilGlCompressedTexImage2D(
                                                cf_target, i,
                                                S3TC_type, w, h, 0,
                                                mip_size, &DDS_data[byte_offset] );
                                }
                                /*      and move to the next mipmap     */
                                byte_offset += mip_size;
                        }
                        /*      it worked!      */
                        result_string_pointer = "DDS file loaded";
                } else
                {
                        glDeleteTextures( 1, & tex_ID );
                        tex_ID = 0;
                        cf_target = ogl_target_end + 1;
                        result_string_pointer = "DDS file was too small for expected image data";
                }
        }/* end reading each face */
        SOIL_free_image_data( DDS_data );
        if( tex_ID )
        {
                /*      did I have MIPmaps?     */
                if( mipmaps > 0 )
                {
                        /*      instruct OpenGL to use the MIPmaps      */
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
                } else
                {
                        /*      instruct OpenGL _NOT_ to use the MIPmaps        */
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
                }
                /*      does the user want clamping, or wrapping?       */
                if( flags & SOIL_FLAG_TEXTURE_REPEATS )
                {
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT );
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT );
                        glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, GL_REPEAT );
                } else
                {
                        /*      unsigned int clamp_mode = SOIL_CLAMP_TO_EDGE;   */
                        unsigned int clamp_mode = GL_CLAMP;
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_S, clamp_mode );
                        glTexParameteri( opengl_texture_type, GL_TEXTURE_WRAP_T, clamp_mode );
                        glTexParameteri( opengl_texture_type, SOIL_TEXTURE_WRAP_R, clamp_mode );
                }
        }

quick_exit:
        /*      report success or failure       */
        return tex_ID;
}

unsigned int SOIL_direct_load_DDS(
                const char *filename,
                unsigned int reuse_texture_ID,
                int flags,
                int loading_as_cubemap )
{
        FILE *f;
        unsigned char *buffer;
        size_t buffer_length, bytes_read;
        unsigned int tex_ID = 0;
        /*      error checks    */
        if( NULL == filename )
        {
                result_string_pointer = "NULL filename";
                return 0;
        }
        f = fopen( filename, "rb" );
        if( NULL == f )
        {
                /*      the file doesn't seem to exist (or be open-able)        */
                result_string_pointer = "Can not find DDS file";
                return 0;
        }
        fseek( f, 0, SEEK_END );
        buffer_length = ftell( f );
        fseek( f, 0, SEEK_SET );
        buffer = (unsigned char *) malloc( buffer_length );
        if( NULL == buffer )
        {
                result_string_pointer = "malloc failed";
                fclose( f );
                return 0;
        }
        bytes_read = fread( (void*)buffer, 1, buffer_length, f );
        fclose( f );
        if( bytes_read < buffer_length )
        {
                /*      huh?    */
                buffer_length = bytes_read;
        }
        /*      now try to do the loading       */
        tex_ID = SOIL_direct_load_DDS_from_memory(
                (const unsigned char *const)buffer, buffer_length,
                reuse_texture_ID, flags, loading_as_cubemap );
        SOIL_free_image_data( buffer );
        return tex_ID;
}

int query_NPOT_capability( void )
{
        /*      check for the capability        */
        if( has_NPOT_capability == SOIL_CAPABILITY_UNKNOWN )
        {
                /*      we haven't yet checked for the capability, do so        */
                if(
                        (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ),
                                "GL_ARB_texture_non_power_of_two" ) )
                        )
                {
                        /*      not there, flag the failure     */
                        has_NPOT_capability = SOIL_CAPABILITY_NONE;
                } else
                {
                        /*      it's there!     */
                        has_NPOT_capability = SOIL_CAPABILITY_PRESENT;
                }
        }
        /*      let the user know if we can do non-power-of-two textures or not */
        return has_NPOT_capability;
}

int query_tex_rectangle_capability( void )
{
        /*      check for the capability        */
        if( has_tex_rectangle_capability == SOIL_CAPABILITY_UNKNOWN )
        {
                /*      we haven't yet checked for the capability, do so        */
                if(
                        (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ),
                                "GL_ARB_texture_rectangle" ) )
                &&
                        (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ),
                                "GL_EXT_texture_rectangle" ) )
                &&
                        (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ),
                                "GL_NV_texture_rectangle" ) )
                        )
                {
                        /*      not there, flag the failure     */
                        has_tex_rectangle_capability = SOIL_CAPABILITY_NONE;
                } else
                {
                        /*      it's there!     */
                        has_tex_rectangle_capability = SOIL_CAPABILITY_PRESENT;
                }
        }
        /*      let the user know if we can do texture rectangles or not        */
        return has_tex_rectangle_capability;
}

int query_cubemap_capability( void )
{
        /*      check for the capability        */
        if( has_cubemap_capability == SOIL_CAPABILITY_UNKNOWN )
        {
                /*      we haven't yet checked for the capability, do so        */
                if(
                        (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ),
                                "GL_ARB_texture_cube_map" ) )
                &&
                        (NULL == strstr( (char const*)glGetString( GL_EXTENSIONS ),
                                "GL_EXT_texture_cube_map" ) )
                        )
                {
                        /*      not there, flag the failure     */
                        has_cubemap_capability = SOIL_CAPABILITY_NONE;
                } else
                {
                        /*      it's there!     */
                        has_cubemap_capability = SOIL_CAPABILITY_PRESENT;
                }
        }
        /*      let the user know if we can do cubemaps or not  */
        return has_cubemap_capability;
}

int query_DXT_capability( void )
{
        /*      check for the capability        */
        if( has_DXT_capability == SOIL_CAPABILITY_UNKNOWN )
        {
                /*      we haven't yet checked for the capability, do so        */
                if( NULL == strstr(
                                (char const*)glGetString( GL_EXTENSIONS ),
                                "GL_EXT_texture_compression_s3tc" ) )
                {
                        /*      not there, flag the failure     */
                        has_DXT_capability = SOIL_CAPABILITY_NONE;
                } else
                {
                        /*      and find the address of the extension function  */
                        P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC ext_addr = NULL;
                        #ifdef WIN32
                                ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)
                                                wglGetProcAddress
                                                (
                                                        "glCompressedTexImage2DARB"
                                                );
                        #elif defined(__APPLE__) || defined(__APPLE_CC__)
                                /*      I can't test this Apple stuff!  */
                                CFBundleRef bundle;
                                CFURLRef bundleURL =
                                        CFURLCreateWithFileSystemPath(
                                                kCFAllocatorDefault,
                                                CFSTR("/System/Library/Frameworks/OpenGL.framework"),
                                                kCFURLPOSIXPathStyle,
                                                true );
                                CFStringRef extensionName =
                                        CFStringCreateWithCString(
                                                kCFAllocatorDefault,
                                                "glCompressedTexImage2DARB",
                                                kCFStringEncodingASCII );
                                bundle = CFBundleCreate( kCFAllocatorDefault, bundleURL );
                                assert( bundle != NULL );
                                ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)
                                                CFBundleGetFunctionPointerForName
                                                (
                                                        bundle, extensionName
                                                );
                                CFRelease( bundleURL );
                                CFRelease( extensionName );
                                CFRelease( bundle );
                        #else
                                ext_addr = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)
                                                glXGetProcAddressARB
                                                (
                                                        (const GLubyte *)"glCompressedTexImage2DARB"
                                                );
                        #endif
                        /*      Flag it so no checks needed later       */
                        if( NULL == ext_addr )
                        {
                                /*      hmm, not good!!  This should not happen, but does on my
                                        laptop's VIA chipset.  The GL_EXT_texture_compression_s3tc
                                        spec requires that ARB_texture_compression be present too.
                                        this means I can upload and have the OpenGL drive do the
                                        conversion, but I can't use my own routines or load DDS files
                                        from disk and upload them directly [8^( */
                                has_DXT_capability = SOIL_CAPABILITY_NONE;
                        } else
                        {
                                /*      all's well!     */
                                soilGlCompressedTexImage2D = ext_addr;
                                has_DXT_capability = SOIL_CAPABILITY_PRESENT;
                        }
                }
        }
        /*      let the user know if we can do DXT or not       */
        return has_DXT_capability;
}