From 6f55ecefc6d692299eee11e366439556cfe33143 Mon Sep 17 00:00:00 2001 From: psperl Date: Sun, 30 Sep 2007 02:14:28 +0000 Subject: [PATCH] new texture library git-svn-id: https://projectm.svn.sourceforge.net/svnroot/projectm/trunk@490 6778bc44-b910-0410-a7a0-be141de4315d --- src/projectM-engine/CMakeLists.txt | 17 +- src/projectM-engine/SOIL.c | 1786 +++++++++++++ src/projectM-engine/SOIL.h | 377 +++ src/projectM-engine/TextureManager.cpp | 47 +- src/projectM-engine/TextureManager.hpp | 7 +- src/projectM-engine/bmpLoader.c | 384 --- src/projectM-engine/bmpLoader.h | 33 - src/projectM-engine/image_DXT.c | 632 +++++ src/projectM-engine/image_DXT.h | 123 + src/projectM-engine/image_helper.c | 151 ++ src/projectM-engine/image_helper.h | 50 + src/projectM-engine/pcxLoader.c | 349 --- src/projectM-engine/pcxLoader.h | 33 - src/projectM-engine/stb_image.c | 3283 ++++++++++++++++++++++++ src/projectM-engine/stb_image.h | 253 ++ src/projectM-engine/texture.cpp | 268 -- src/projectM-engine/texture.h | 48 - src/projectM-engine/tgaLoader.c | 597 ----- src/projectM-engine/tgaLoader.h | 33 - 19 files changed, 6708 insertions(+), 1763 deletions(-) create mode 100644 src/projectM-engine/SOIL.c create mode 100644 src/projectM-engine/SOIL.h delete mode 100644 src/projectM-engine/bmpLoader.c delete mode 100644 src/projectM-engine/bmpLoader.h create mode 100644 src/projectM-engine/image_DXT.c create mode 100644 src/projectM-engine/image_DXT.h create mode 100644 src/projectM-engine/image_helper.c create mode 100644 src/projectM-engine/image_helper.h delete mode 100644 src/projectM-engine/pcxLoader.c delete mode 100644 src/projectM-engine/pcxLoader.h create mode 100644 src/projectM-engine/stb_image.c create mode 100644 src/projectM-engine/stb_image.h delete mode 100644 src/projectM-engine/texture.cpp delete mode 100644 src/projectM-engine/texture.h delete mode 100644 src/projectM-engine/tgaLoader.c delete mode 100644 src/projectM-engine/tgaLoader.h diff --git a/src/projectM-engine/CMakeLists.txt b/src/projectM-engine/CMakeLists.txt index 3eb46f6ba..0eaf38980 100644 --- a/src/projectM-engine/CMakeLists.txt +++ b/src/projectM-engine/CMakeLists.txt @@ -1,14 +1,11 @@ PROJECT(projectM) -ADD_LIBRARY(projectM SHARED projectM.cpp projectM.hpp PBuffer.cpp PBuffer.hpp InitCond.cpp InitCond.hpp - Expr.cpp PCM.cpp Parser.cpp Preset.cpp Common.hpp BeatDetect.cpp PCM.hpp PerPixelEqn.cpp Eval.hpp -RingBuffer.hpp Param.cpp CustomWave.cpp CustomShape.hpp CustomShape.cpp Param.hpp CustomWave.hpp BeatDetect.hpp -KeyHandler.hpp -Func.hpp Func.cpp Eval.cpp wipemalloc.h PerFrameEqn.cpp PerPointEqn.cpp fftsg.cpp KeyHandler.cpp -CValue.hpp Expr.hpp timer.cpp wipemalloc.cpp PerFrameEqn.hpp PerPixelEqn.hpp PerPointEqn.hpp BuiltinFuncs.hpp -BuiltinFuncs.cpp compare.h event.h fatal.h fftsg.h timer.h BuiltinParams.hpp BuiltinParams.cpp Preset.hpp Renderer.cpp Renderer.hpp ParamUtils.hpp -PresetLoader.cpp PresetLoader.hpp PresetChooser.hpp PresetChooser.cpp PresetFrameIO.cpp PresetFrameIO.hpp PresetMerge.cpp PresetMerge.hpp -ConfigFile.h ConfigFile.cpp IdlePreset.hpp IdlePreset.cpp bmpLoader.h bmpLoader.c pcxLoader.c pcxLoader.h texture.cpp texture.h tgaLoader.c -tgaLoader.h TextureManager.cpp TextureManager.hpp MoodBar.hpp MoodBar.cpp) +ADD_LIBRARY(projectM SHARED projectM.cpp PBuffer.cpp InitCond.cpp + Expr.cpp PCM.cpp Parser.cpp Preset.cpp BeatDetect.cpp PerPixelEqn.cpp Param.cpp CustomWave.cpp CustomShape.cpp +Func.cpp Eval.cpp PerFrameEqn.cpp PerPointEqn.cpp fftsg.cpp KeyHandler.cpp + timer.cpp wipemalloc.cpp +BuiltinFuncs.cpp BuiltinParams.cpp Renderer.cpp +PresetLoader.cpp PresetChooser.cpp PresetFrameIO.cpp PresetMerge.cpp +ConfigFile.cpp IdlePreset.cpp TextureManager.cpp MoodBar.cpp image_DXT.c image_helper.c SOIL.c stb_image.c ) OPTION(USE_FTGL "Use FTGL for on-screen fonts" ON) diff --git a/src/projectM-engine/SOIL.c b/src/projectM-engine/SOIL.c new file mode 100644 index 000000000..ef0201eda --- /dev/null +++ b/src/projectM-engine/SOIL.c @@ -0,0 +1,1786 @@ +/* + 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 +*/ + +#ifdef WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #include + #include +#elif defined(__APPLE__) || defined(__APPLE_CC__) + /* I can't test this Apple stuff! */ + #include + #include +#else + #include + #include +#endif + +#include "SOIL.h" +#include "stb_image.h" +#include "image_helper.h" +#include "image_DXT.h" + +#include +#include + +#define SOIL_CHECK_FOR_GL_ERRORS 0 + +/* error reporting */ +char *result_string_pointer = "SOIL initialized"; + +/* for loading cube maps */ +enum{ + SOIL_CUBEMAP_UNKNOWN = -1, + SOIL_CUBEMAP_NONE = 0, + SOIL_CUBEMAP_PRESENT = 1 +}; +static int has_cubemap_capability = SOIL_CUBEMAP_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 using DXT compression */ +enum{ + SOIL_DXT_UNKNOWN = -1, + SOIL_DXT_NONE = 0, + SOIL_DXT_COMPRESS = 1, + SOIL_DXT_DIRECT_UPLOAD = 2 +}; +static int has_DXT_capability = SOIL_DXT_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 ); + 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_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 ); + 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_CUBEMAP_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 ); + 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 ); + 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 ); + 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 ); + 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 ); + 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 ); + 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_CUBEMAP_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 ); + 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 ); + 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 ); + 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 ); + 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 ); + 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 ); + 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_CUBEMAP_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 ); + 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_CUBEMAP_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 ); + 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_CUBEMAP_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(); + } +} +#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_DXT_NONE; + int max_supported_size; + /* 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 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; + } + } + /* 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 ); + /* 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; + } + /* 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 ); + } + #if SOIL_CHECK_FOR_GL_ERRORS + check_for_GL_errors( "glGenTextures" ); + #endif + /* 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_DXT_NONE ) + { + /* 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 ); + #if SOIL_CHECK_FOR_GL_ERRORS + check_for_GL_errors( "glBindTexture" ); + #endif + /* upload the main image */ + if( DXT_mode == SOIL_DXT_DIRECT_UPLOAD ) + { + /* 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 ); + #if SOIL_CHECK_FOR_GL_ERRORS + check_for_GL_errors( "glCompressedTexImage2D" ); + #endif + 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 ); + #if SOIL_CHECK_FOR_GL_ERRORS + check_for_GL_errors( "glTexImage2D" ); + #endif + /* 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 ); + #if SOIL_CHECK_FOR_GL_ERRORS + check_for_GL_errors( "glTexImage2D" ); + #endif + /*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< 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( (char*)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( + (stbi_uc *)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( (char*)filename, + width, height, channels, (void*)data ); + } else + if( image_type == SOIL_SAVE_TYPE_TGA ) + { + save_result = stbi_write_tga( (char*)filename, + width, height, channels, (void*)data ); + } else + if( image_type == SOIL_SAVE_TYPE_DDS ) + { + save_result = save_image_as_DDS( (const char*)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; +} + +int query_cubemap_capability( void ) +{ + /* check for the capability */ + if( has_cubemap_capability == SOIL_CUBEMAP_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_CUBEMAP_NONE; + } else + { + /* it's there! */ + has_cubemap_capability = SOIL_CUBEMAP_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_DXT_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_DXT_NONE; + } else + { + /* and find the address of the extension function */ + void *ext_addr = NULL; + #ifdef WIN32 + ext_addr = (void*)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 = CFBundleGetFunctionPointerForName( + bundle, extensionName ); + CFRelease( bundleURL ); + CFRelease( functionName ); + CFRelease( bundle ); + #else + ext_addr = (void*)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_DXT_COMPRESS; + } else + { + /* all's well! */ + soilGlCompressedTexImage2D = (P_SOIL_GLCOMPRESSEDTEXIMAGE2DPROC)ext_addr; + has_DXT_capability = SOIL_DXT_DIRECT_UPLOAD; + } + } + } + /* let the user know if we can do DXT or not */ + return has_DXT_capability; +} + +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_DXT_DIRECT_UPLOAD ) + { + /* 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_CUBEMAP_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 <= 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 < 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; +} diff --git a/src/projectM-engine/SOIL.h b/src/projectM-engine/SOIL.h new file mode 100644 index 000000000..4a9cca01b --- /dev/null +++ b/src/projectM-engine/SOIL.h @@ -0,0 +1,377 @@ +/** + @mainpage SOIL + + Jonathan Dummer + 2007-07-26-10.36 + + Simple OpenGL Image Library + + A tiny c library for uploading images as + textures into OpenGL. Also saving and + loading of images is supported. + + I'm using Sean's Tool Box image loader as a base: + http://www.nothings.org/ + + I'm upgrading it to load TGA and DDS files, and a direct + path for loading DDS files straight into OpenGL textures, + when applicable. + + Image Formats: + - BMP load & save + - TGA load & save + - DDS load & save + - PNG load + - JPG load + + OpenGL Texture Features: + - resample to power-of-two sizes + - MIPmap generation + - compressed texture S3TC formats (if supported) + - can pre-multiply alpha for you, for better compositing + - can flip image about the y-axis (except pre-compressed DDS files) + + 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 +**/ + +#ifndef HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY +#define HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY + +#ifdef __cplusplus +extern "C" { +#endif + +/** + The format of images that may be loaded (force_channels). + SOIL_LOAD_AUTO leaves the image in whatever format it was found. + SOIL_LOAD_L forces the image to load as Luminous (greyscale) + SOIL_LOAD_LA forces the image to load as Luminous with Alpha + SOIL_LOAD_RGB forces the image to load as Red Green Blue + SOIL_LOAD_RGBA forces the image to load as Red Green Blue Alpha +**/ +enum +{ + SOIL_LOAD_AUTO = 0, + SOIL_LOAD_L = 1, + SOIL_LOAD_LA = 2, + SOIL_LOAD_RGB = 3, + SOIL_LOAD_RGBA = 4 +}; + +/** + Passed in as reuse_texture_ID, will cause SOIL to + register a new texture ID using glGenTextures(). + If the value passed into reuse_texture_ID > 0 then + SOIL will just re-use that texture ID (great for + reloading image assets in-game!) +**/ +enum +{ + SOIL_CREATE_NEW_ID = 0 +}; + +/** + flags you can pass into SOIL_load_OGL_texture() + and SOIL_create_OGL_texture() + (note that if SOIL_FLAG_DDS_LOAD_DIRECT is used + the rest of the flags with the exception of + SOIL_FLAG_TEXTURE_REPEATS will be ignored while + loading already-compressed DDS files) +**/ +enum +{ + SOIL_FLAG_POWER_OF_TWO = 1, + SOIL_FLAG_MIPMAPS = 2, + SOIL_FLAG_TEXTURE_REPEATS = 4, + SOIL_FLAG_MULTIPLY_ALPHA = 8, + SOIL_FLAG_INVERT_Y = 16, + SOIL_FLAG_COMPRESS_TO_DXT = 32, + SOIL_FLAG_DDS_LOAD_DIRECT = 64 +}; + +/** + The types of images that may be saved. + (TGA supports uncompressed RGB / RGBA) + (BMP supports uncompressed RGB) +**/ +enum +{ + SOIL_SAVE_TYPE_TGA = 0, + SOIL_SAVE_TYPE_BMP, + SOIL_SAVE_TYPE_DDS +}; + +/** + Defines the order of faces in a DDS cubemap. + I recommend that you use the same order in single + image cubemap files, so they will be interchangeable + with DDS cubemaps when using SOIL. +**/ +const char SOIL_DDS_CUBEMAP_FACE_ORDER[] = + { 'E', 'W', 'U', 'D', 'N', 'S' }; + +/** + Loads an image from disk into an OpenGL texture. + \param filename the name of the file to upload as a texture + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +unsigned int + SOIL_load_OGL_texture + ( + const char *filename, + int force_channels, + unsigned int reuse_texture_ID, + unsigned int flags + ); + +/** + Loads 6 images from disk into an OpenGL cubemap texture. + \param x_pos_file the name of the file to upload as the +x cube face + \param x_neg_file the name of the file to upload as the -x cube face + \param y_pos_file the name of the file to upload as the +y cube face + \param y_neg_file the name of the file to upload as the -y cube face + \param z_pos_file the name of the file to upload as the +z cube face + \param z_neg_file the name of the file to upload as the -z cube face + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +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 + ); + +/** + Loads 1 image from disk and splits it into an OpenGL cubemap texture. + \param filename the name of the file to upload as a texture + \param face_order the order of the faces in the file, any combination of NSWEUD, for North, South, Up, etc. + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +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 + ); + +/** + Loads an image from RAM into an OpenGL texture. + \param buffer the image data in RAM just as if it were still in a file + \param buffer_length the size of the buffer in bytes + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +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 + ); + +/** + Loads 6 images from memory into an OpenGL cubemap texture. + \param x_pos_buffer the image data in RAM to upload as the +x cube face + \param x_pos_buffer_length the size of the above buffer + \param x_neg_buffer the image data in RAM to upload as the +x cube face + \param x_neg_buffer_length the size of the above buffer + \param y_pos_buffer the image data in RAM to upload as the +x cube face + \param y_pos_buffer_length the size of the above buffer + \param y_neg_buffer the image data in RAM to upload as the +x cube face + \param y_neg_buffer_length the size of the above buffer + \param z_pos_buffer the image data in RAM to upload as the +x cube face + \param z_pos_buffer_length the size of the above buffer + \param z_neg_buffer the image data in RAM to upload as the +x cube face + \param z_neg_buffer_length the size of the above buffer + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +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 + ); + +/** + Loads 1 image from RAM and splits it into an OpenGL cubemap texture. + \param buffer the image data in RAM just as if it were still in a file + \param buffer_length the size of the buffer in bytes + \param face_order the order of the faces in the file, any combination of NSWEUD, for North, South, Up, etc. + \param force_channels 0-image format, 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +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 + ); + +/** + Creates a 2D OpenGL texture from raw image data. Note that the raw data is + _NOT_ freed after the upload (so the user can load various versions). + \param data the raw data to be uploaded as an OpenGL texture + \param width the width of the image in pixels + \param height the height of the image in pixels + \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +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 + ); + +/** + Creates an OpenGL cubemap texture by splitting up 1 image into 6 parts. + \param data the raw data to be uploaded as an OpenGL texture + \param width the width of the image in pixels + \param height the height of the image in pixels + \param channels the number of channels: 1-luminous, 2-luminous/alpha, 3-RGB, 4-RGBA + \param face_order the order of the faces in the file, and combination of NSWEUD, for North, South, Up, etc. + \param reuse_texture_ID 0-generate a new texture ID, otherwise reuse the texture ID (overwriting the old texture) + \param flags can be any of SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT | SOIL_FLAG_DDS_LOAD_DIRECT + \return 0-failed, otherwise returns the OpenGL texture handle +**/ +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 + ); + +/** + Captures the OpenGL window (RGB) and saves it to disk + \return 0 if it failed, otherwise returns 1 +**/ +int + SOIL_save_screenshot + ( + const char *filename, + int image_type, + int x, int y, + int width, int height + ); + +/** + Loads an image from disk into an array of unsigned chars. + \return 0 if failed, otherwise returns 1 +**/ +unsigned char* + SOIL_load_image + ( + const char *filename, + int *width, int *height, int *channels, + int force_channels + ); + +/** + Loads an image from memory into an array of unsigned chars. + \return 0 if failed, otherwise returns 1 +**/ +unsigned char* + SOIL_load_image_from_memory + ( + const unsigned char *const buffer, + int buffer_length, + int *width, int *height, int *channels, + int force_channels + ); + +/** + Saves an image from an array of unsigned chars (RGBA) to disk + \return 0 if failed, otherwise returns 1 +**/ +int + SOIL_save_image + ( + const char *filename, + int image_type, + int width, int height, int channels, + const unsigned char *const data + ); + +/** + Frees the image data (note, this is just C's "free()"...this function is + present mostly so C++ programmers don't forget to use "free()" and call + "delete []" instead [8^) +**/ +void + SOIL_free_image_data + ( + unsigned char *img_data + ); + +/** + This function resturn a pointer to a string describing the last thing + that happened inside SOIL. It can be used to determine why an image + failed to load. +**/ +const char* + SOIL_last_result + ( + void + ); + + +#ifdef __cplusplus +} +#endif + +#endif /* HEADER_SIMPLE_OPENGL_IMAGE_LIBRARY */ diff --git a/src/projectM-engine/TextureManager.cpp b/src/projectM-engine/TextureManager.cpp index 34db31df7..46a7ab259 100644 --- a/src/projectM-engine/TextureManager.cpp +++ b/src/projectM-engine/TextureManager.cpp @@ -1,16 +1,30 @@ #include "TextureManager.hpp" #include "CustomShape.hpp" #include "Common.hpp" -#include + + + TextureManager::TextureManager(const std::string _presetURL): presetURL(_presetURL) { ; } +TextureManager::~TextureManager() +{ + // DeleteAllTextures(); + + std::map::const_iterator iter; + + for(iter = textures.begin(); iter != textures.end(); iter++) + { + glDeleteTextures(1,&iter->second); + } +} void TextureManager::unloadTextures(const PresetOutputs::cshape_container &shapes) { + /* for (PresetOutputs::cshape_container::const_iterator pos = shapes.begin(); pos != shapes.end(); ++pos) { @@ -29,16 +43,39 @@ void TextureManager::unloadTextures(const PresetOutputs::cshape_container &shape } } } + */ } -GLuint TextureManager::getTexture(const std::string imageUrl) +GLuint TextureManager::getTexture(const std::string imageURL) { - std::string fullUrl = presetURL + PATH_SEPARATOR + imageUrl; - return LoadTexture(fullUrl.c_str()); + + if (textures.find(imageURL)!= textures.end()) + { + return textures[imageURL]; + } + else + { + std::string fullURL = presetURL + PATH_SEPARATOR + imageURL; + GLuint tex = SOIL_load_OGL_texture( + fullURL.c_str(), + SOIL_LOAD_AUTO, + SOIL_CREATE_NEW_ID, + SOIL_FLAG_POWER_OF_TWO + //| SOIL_FLAG_MIPMAPS + | SOIL_FLAG_MULTIPLY_ALPHA + | SOIL_FLAG_COMPRESS_TO_DXT + //| SOIL_FLAG_DDS_LOAD_DIRECT + ); + textures[imageURL]=tex; + return tex; + + + } + // return LoadTexture(fullUrl.c_str(),false); } unsigned int TextureManager::getTextureMemorySize() { - return GetTotalTextureSize(); + return 0;//GetTotalTextureSize(); } diff --git a/src/projectM-engine/TextureManager.hpp b/src/projectM-engine/TextureManager.hpp index 0bcfc9d34..212012751 100644 --- a/src/projectM-engine/TextureManager.hpp +++ b/src/projectM-engine/TextureManager.hpp @@ -8,16 +8,17 @@ #ifdef WIN32 #include #endif -#include "texture.h" +#include "SOIL.h" #include #include +#include class TextureManager { std::string presetURL; - + std::map textures; public: - + ~TextureManager(); TextureManager(std::string _presetURL); void unloadTextures(const PresetOutputs::cshape_container &shapes); GLuint getTexture(std::string imageUrl); diff --git a/src/projectM-engine/bmpLoader.c b/src/projectM-engine/bmpLoader.c deleted file mode 100644 index 279cd6274..000000000 --- a/src/projectM-engine/bmpLoader.c +++ /dev/null @@ -1,384 +0,0 @@ -#include "bmpLoader.h" - -#include -#include -#include -#include - - -/* force 2 byte alignment of structures */ -#pragma pack(push,2) - -/*! - * This structure comes first in the bitmap file. It's a pretty boring structure really.... - */ -typedef struct -{ - /*! - * The characters identifying the bitmap. The following entries are possible: - * - * 'BM' - Windows 3.1x, 95, NT, . - * 'BA' - OS/2 Bitmap Array - * 'CI' - OS/2 Color Icon - * 'CP' - OS/2 Color Pointer - * 'IC' - OS/2 Icon - * 'PT' - OS/2 Pointer - */ - unsigned char Identifier[2]; - - /*! - * Complete file size in bytes - */ - unsigned long FileSize; - - /*! - * Reserved for later use - */ - unsigned long Reserved; - - /*! - * Offset from beginning of file to the beginning of the bitmap data - */ - unsigned long BitmapDataOffset; -} -BitmapFileHeader; - - -/*! - * This structure immediately follows the main file header. This basically describes - * the information contained within - */ -typedef struct -{ - /*! - * Length of the Bitmap Info Header used to describe the bitmap colors, compression, - * The following sizes are possible: - * - * 28h - Windows 3.1x, 95, NT, . - * 0Ch - OS/2 1.x - * F0h - OS/2 2.x - */ - unsigned long BitmapHeaderSize; - - /*! - * Horizontal width of bitmap in pixels. - */ - long Width; - - /*! - * Vertical height of bitmap in pixels. - */ - long Height; - - /*! - * Number of planes in this bitmap. - */ - unsigned short Planes; - - /*! - * Bits per pixel used to store palette entry information. This also identifies - * in an indirect way the number of possible colors. Possible values are: - * - * 1 - Monochrome bitmap - * 4 - 16 color bitmap - * 8 - 256 color bitmap - * 16 - 16bit (high color) bitmap - * 24 - 24bit (true color) bitmap - * 32 - 32bit (true color) bitmap - */ - unsigned short BitsPerPixel; - - /*! - * Compression specifications. The following values are possible: - * - * 0 - none (Also identified by BI_RGB) - * 1 - RLE 8-bit / pixel (Also identified by BI_RLE4) - * 2 - RLE 4-bit / pixel (Also identified by BI_RLE8) - * 3 - Bitfields (Also identified by BI_BITFIELDS) - */ - unsigned long Compression; - - /*! - * Size of the bitmap data in bytes. This number must be rounded to the next 4 byte boundary. - */ - unsigned long BitmapDataSize; - - /*! - * Horizontal resolution expressed in pixel per meter. - */ - unsigned long HResolution; - - /*! - * Vertical resolution expressed in pixels per meter. - */ - unsigned long VResolution; - - /*! - * Number of colors used by this bitmap. For a 8-bit / pixel - * bitmap this will be 100h or 256. - */ - unsigned long ColorsUsed; - - /*! - * Number of important colors. This number will be equal to the - * number of colors when every color is important. - */ - unsigned long ImportantColors; -} -BitmapInfoHeader; - - -/*! - * This structure is used to hold a colour map entry. - */ -typedef struct -{ - /*! blue component */ - unsigned char b; - - /*! green component */ - unsigned char g; - - /*! red component */ - unsigned char r; - - /*! some say alpha some say reserved.... */ - unsigned char a; -} -ColorMapEntry; - -#pragma pack(pop) - - - -/*! - * \brief This function loads a bitmap image - * \param filename - - */ -int LoadBmpImage(const char filename[], - unsigned char** pixels, - unsigned int* w, - unsigned int* h, - unsigned int* bpp) -{ - BitmapFileHeader FileHeader; - BitmapInfoHeader ImageHeader; - - FILE *fp = fopen( filename, "rb" ); - - /* ensure file is a happy file... */ - if( fp == NULL ) - { - return 0; - } - - /* read the bitmap file */ - fread( &FileHeader, sizeof(BitmapFileHeader), 1, fp ); - - /* - * Make sure we have a valid bitmap file - */ - if( FileHeader.Identifier[0] != 'B' || - FileHeader.Identifier[1] != 'M' ) - { - return 0; - } - - /* - * Read the image header. - */ - fread( &ImageHeader, sizeof(BitmapInfoHeader), 1, fp ); - - if( ImageHeader.Compression != 0 ) - { - printf("[ERROR] This bitmap loader does not support compressed files (yet)\n"); - return 0; - } - - /* store some useful info */ - *w = ImageHeader.Width; - *h = ImageHeader.Height; - - - /*---------------------------------------------------------------------------- 8 Bit Colour mapped images */ - - /* - * It appears as though all 8bit bmp images will have a colour map. This is - * a tad bit annoying as a grey scale image would have also been nice! - */ - if( ImageHeader.BitsPerPixel == 8 ) - { - /* pointer for colour map data */ - ColorMapEntry* map; - - /* hopefully if we have a colour map it will be a sensible 8bit one... */ - if( ImageHeader.BitsPerPixel != 8 ) - { - fprintf(stderr,"[ERROR] The bitmap file you are trying to load has a really odd colour map definition!!\n"); - } - - /* allocate space for colour map */ - map = (ColorMapEntry*) malloc( sizeof(ColorMapEntry) * 256 ); - assert( map ); - - /* read colour map */ - fread( map, sizeof(ColorMapEntry), 256, fp ); - - { - unsigned int i; - for(i=0;i<256;++i) - { - /* if not a grey colour ignore greyscale reading */ - if( i != map[i].r && - i != map[i].g && - i != map[i].b ) { - goto hacky_hacky_moo; - } - } - - /* ignore colour map */ - *bpp = 1; - free( map ); - - /* allocate memory for the pixel data */ - *pixels = (unsigned char*) malloc( (*w) * (*h) ); - assert(*pixels); - fread( *pixels, (*w) * (*h), 1, fp ); - return 1; - } - - hacky_hacky_moo: ; - - /* - * Bitmap files are rather irritatingly aligned to 4 bytes on a line.... - */ - { - unsigned int i; - unsigned int RowSize = ImageHeader.Width; - unsigned char *scanline; - - if( RowSize%4 != 0 ) - { - RowSize += (4 - RowSize%4); - } - - /* allocate enough memory for a scanline */ - scanline = (unsigned char*) malloc( RowSize ); - assert(scanline); - - /* we will expand the colour map to 24bit */ - *bpp = 3; - - /* allocate memory for the pixel data */ - *pixels = (unsigned char*) malloc( (*w) * (*h) * (*bpp) ); - assert(*pixels); - - /* loop through each scanline */ - for(i=0;i<(*h); ++i) - { - /* get a pointer to the start of the scanline in expanded data */ - unsigned char* pline = ( (*pixels) + ( 3 * (*w) * i ) ); - unsigned int j; - - /* read the scanline including padding */ - fread( scanline, RowSize, 1, fp ); - - /* go through each pixel in line */ - for( j=0; j<(*w); ++j ) - { - /* copy red from colour map to pixel data array */ - (*pline) = map[ scanline[j] ].r; - ++pline; - - /* copy green from colour map to pixel data array */ - (*pline) = map[ scanline[j] ].g; - ++pline; - - /* copy blue from colour map to pixel data array */ - (*pline) = map[ scanline[j] ].b; - ++pline; - } - } - - /* delete the dynamic data */ - free( scanline ); - free( map ); - } - fclose(fp); - return 1; - } - - - /*---------------------------------------------------------------------------- 24/32 Bit uncompressed images */ - - { - unsigned int bdepth; - unsigned int i; - unsigned int RowSize; - unsigned int padding = 0; - - /* determine the bitdepth of the actual bitmap image */ - switch( ImageHeader.BitsPerPixel ) - { - case 24: - bdepth=3; - break; - - case 32: - bdepth=4; - break; - - default: - printf("[ERROR] bitmap loader does not support this bit depth\n"); - fclose(fp); - return 0; - } - *bpp = bdepth; - - /* we will need this value a fair amount, so pre-calculate it... */ - RowSize = bdepth * ImageHeader.Width; - - /* determine the padding at the end of the scanline */ - if( (RowSize % 4) != 0 ) - { - padding = 4 - (RowSize % 4); - } - - - - /* allocate memory for the pixel data */ - *pixels = (unsigned char*) malloc( (*h) * RowSize ); - assert(*pixels); - - for( i=0; i != *h; ++i ) - { - unsigned char* end; - /* get a pointer to the start of the scanline in expanded data */ - unsigned char* pline = ( (*pixels) + ( RowSize * i ) ); - unsigned int j; - - /* read the scanline including padding */ - fread( pline, RowSize, 1, fp ); - - end = pline + RowSize; - - /* flip scanline from BGR to RGB */ - for( ; pline != end ; pline += bdepth ) - { - unsigned char temp = *pline; - pline[0] = pline[2]; - pline[2] = temp; - } - - /* now read past padding */ - for( j=0; j!= padding; ++j ) - { - fgetc(fp); - } - } - } - - fclose(fp); - - return 1; -} diff --git a/src/projectM-engine/bmpLoader.h b/src/projectM-engine/bmpLoader.h deleted file mode 100644 index 47c40969d..000000000 --- a/src/projectM-engine/bmpLoader.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ROBS_BMP_LOADER___H_ -#define ROBS_BMP_LOADER___H_ - - -#ifdef __cplusplus -extern "C" { -#endif - - - -/*! - * \brief This function loads a Bitmap image file. - * \param filename - the name of the file to load - * \param pixels - pointer to the pointer that will hold the pixel data (yuck) - * \param w - the width of the image file - * \param h - the height of the image file - * \param bpp - the bytes per pixel of the image file. - * \return 1 if ok, 0 otherwise - */ -int LoadBmpImage( const char filename[], - unsigned char** pixels, - unsigned int* w, - unsigned int* h, - unsigned int* bpp); - - -#ifdef __cplusplus -} -#endif - - -#endif - diff --git a/src/projectM-engine/image_DXT.c b/src/projectM-engine/image_DXT.c new file mode 100644 index 000000000..666c54244 --- /dev/null +++ b/src/projectM-engine/image_DXT.c @@ -0,0 +1,632 @@ +/* + Jonathan Dummer + 2007-07-31-10.32 + + simple DXT compression / decompression code + + public domain +*/ + +#include "image_DXT.h" +#include +#include +#include +#include + +/* set this =1 if you want to use the covarince matrix method... + which is better than my method of using standard deviations + overall, except on the infintesimal chance that the power + method fails for finding the largest eigenvector */ +#define USE_COV_MAT 1 + +/********* Function Prototypes *********/ +/* + Takes a 4x4 block of pixels and compresses it into 8 bytes + in DXT1 format (color only, no alpha). Speed is valued + over prettyness, at least for now. +*/ +void compress_DDS_color_block( + int channels, + const unsigned char *const uncompressed, + unsigned char compressed[8] ); +/* + Takes a 4x4 block of pixels and compresses the alpha + component it into 8 bytes for use in DXT5 DDS files. + Speed is valued over prettyness, at least for now. +*/ +void compress_DDS_alpha_block( + const unsigned char *const uncompressed, + unsigned char compressed[8] ); + +/********* Actual Exposed Functions *********/ +int + save_image_as_DDS + ( + const char *filename, + int width, int height, int channels, + const unsigned char *const data + ) +{ + /* variables */ + FILE *fout; + unsigned char *DDS_data; + DDS_header header; + int DDS_size; + /* error check */ + if( (NULL == filename) || + (width < 1) || (height < 1) || + (channels < 1) || (channels > 4) || + (data == NULL ) ) + { + return 0; + } + /* Convert the image */ + if( (channels & 1) == 1 ) + { + /* no alpha, just use DXT1 */ + DDS_data = convert_image_to_DXT1( data, width, height, channels, &DDS_size ); + } else + { + /* has alpha, so use DXT5 */ + DDS_data = convert_image_to_DXT5( data, width, height, channels, &DDS_size ); + } + /* save it */ + memset( &header, 0, sizeof( DDS_header ) ); + header.dwMagic = ('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24); + header.dwSize = 124; + header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE; + header.dwWidth = width; + header.dwHeight = height; + header.dwPitchOrLinearSize = DDS_size; + header.sPixelFormat.dwSize = 32; + header.sPixelFormat.dwFlags = DDPF_FOURCC; + if( (channels & 1) == 1 ) + { + header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('1' << 24); + } else + { + header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('5' << 24); + } + header.sCaps.dwCaps1 = DDSCAPS_TEXTURE; + /* write it out */ + fout = fopen( filename, "wb"); + fwrite( &header, sizeof( DDS_header ), 1, fout ); + fwrite( DDS_data, 1, DDS_size, fout ); + fclose( fout ); + /* done */ + free( DDS_data ); + return 1; +} + +unsigned char* convert_image_to_DXT1( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size ) +{ + unsigned char *compressed; + int i, j, x, y; + unsigned char ublock[16*3]; + unsigned char cblock[8]; + int index = 0, chan_step = 1; + int block_count = 0; + /* error check */ + *out_size = 0; + if( (width < 1) || (height < 1) || + (NULL == uncompressed) || + (channels < 1) || (channels > 4) ) + { + return NULL; + } + /* for channels == 1 or 2, I do not step forward for R,G,B values */ + if( channels < 3 ) + { + chan_step = 0; + } + /* get the RAM for the compressed image + (8 bytes per 4x4 pixel block) */ + *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 8; + compressed = (unsigned char*)malloc( *out_size ); + /* go through each block */ + for( j = 0; j < height; j += 4 ) + { + for( i = 0; i < width; i += 4 ) + { + /* copy this block into a new one */ + int idx = 0; + int mx = 4, my = 4; + if( j+4 >= height ) + { + my = height - j; + } + if( i+4 >= width ) + { + mx = width - i; + } + for( y = 0; y < my; ++y ) + { + for( x = 0; x < mx; ++x ) + { + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step]; + } + for( x = mx; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + } + } + for( y = my; y < 4; ++y ) + { + for( x = 0; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + } + } + /* compress the block */ + ++block_count; + compress_DDS_color_block( 3, ublock, cblock ); + /* copy the data from the block into the main block */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + } + } + return compressed; +} + +unsigned char* convert_image_to_DXT5( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size ) +{ + unsigned char *compressed; + int i, j, x, y; + unsigned char ublock[16*4]; + unsigned char cblock[8]; + int index = 0, chan_step = 1; + int block_count = 0, has_alpha; + /* error check */ + *out_size = 0; + if( (width < 1) || (height < 1) || + (NULL == uncompressed) || + (channels < 1) || ( channels > 4) ) + { + return NULL; + } + /* for channels == 1 or 2, I do not step forward for R,G,B vales */ + if( channels < 3 ) + { + chan_step = 0; + } + /* # channels = 1 or 3 have no alpha, 2 & 4 do have alpha */ + has_alpha = 1 - (channels & 1); + /* get the RAM for the compressed image + (16 bytes per 4x4 pixel block) */ + *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 16; + compressed = (unsigned char*)malloc( *out_size ); + /* go through each block */ + for( j = 0; j < height; j += 4 ) + { + for( i = 0; i < width; i += 4 ) + { + /* local variables, and my block counter */ + int idx = 0; + int mx = 4, my = 4; + if( j+4 >= height ) + { + my = height - j; + } + if( i+4 >= width ) + { + mx = width - i; + } + for( y = 0; y < my; ++y ) + { + for( x = 0; x < mx; ++x ) + { + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step]; + ublock[idx++] = + has_alpha * uncompressed[(j+y)*width*channels+(i+x)*channels+channels-1] + + (1-has_alpha)*255; + } + for( x = mx; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + ublock[idx++] = ublock[3]; + } + } + for( y = my; y < 4; ++y ) + { + for( x = 0; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + ublock[idx++] = ublock[3]; + } + } + /* now compress the alpha block */ + compress_DDS_alpha_block( ublock, cblock ); + /* copy the data from the compressed alpha block into the main buffer */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + /* then compress the color block */ + ++block_count; + compress_DDS_color_block( 4, ublock, cblock ); + /* copy the data from the compressed color block into the main buffer */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + } + } + return compressed; +} + +/********* Helper Functions *********/ +int convert_bit_range( int c, int from_bits, int to_bits ) +{ + int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); + return (b + (b >> from_bits)) >> from_bits; +} + +int rgb_to_565( int r, int g, int b ) +{ + return + (convert_bit_range( r, 8, 5 ) << 11) | + (convert_bit_range( g, 8, 6 ) << 05) | + (convert_bit_range( b, 8, 5 ) << 00); +} + +void rgb_888_from_565( unsigned int c, int *r, int *g, int *b ) +{ + *r = convert_bit_range( (c >> 11) & 31, 5, 8 ); + *g = convert_bit_range( (c >> 05) & 63, 6, 8 ); + *b = convert_bit_range( (c >> 00) & 31, 5, 8 ); +} + +void compute_color_line_STDEV( + const unsigned char *const uncompressed, + int channels, + float point[3], float direction[3] ) +{ + const float inv_16 = 1.0f / 16.0f; + int i; + float sum_r = 0.0f, sum_g = 0.0f, sum_b = 0.0f; + float sum_rr = 0.0f, sum_gg = 0.0f, sum_bb = 0.0f; + float sum_rg = 0.0f, sum_rb = 0.0f, sum_gb = 0.0f; + /* calculate all data needed for the covariance matrix + ( to compare with _rygdxt code) */ + for( i = 0; i < 16*channels; i += channels ) + { + sum_r += uncompressed[i+0]; + sum_rr += uncompressed[i+0] * uncompressed[i+0]; + sum_g += uncompressed[i+1]; + sum_gg += uncompressed[i+1] * uncompressed[i+1]; + sum_b += uncompressed[i+2]; + sum_bb += uncompressed[i+2] * uncompressed[i+2]; + sum_rg += uncompressed[i+0] * uncompressed[i+1]; + sum_rb += uncompressed[i+0] * uncompressed[i+2]; + sum_gb += uncompressed[i+1] * uncompressed[i+2]; + } + /* convert the sums to averages */ + sum_r *= inv_16; + sum_g *= inv_16; + sum_b *= inv_16; + /* and convert the squares to the squares of the value - avg_value */ + sum_rr -= 16.0f * sum_r * sum_r; + sum_gg -= 16.0f * sum_g * sum_g; + sum_bb -= 16.0f * sum_b * sum_b; + sum_rg -= 16.0f * sum_r * sum_g; + sum_rb -= 16.0f * sum_r * sum_b; + sum_gb -= 16.0f * sum_g * sum_b; + /* the point on the color line is the average */ + point[0] = sum_r; + point[1] = sum_g; + point[2] = sum_b; + #if USE_COV_MAT + /* + The following idea was from ryg. + (https://mollyrocket.com/forums/viewtopic.php?t=392) + The method worked great (less RMSE than mine) most of + the time, but had some issues handling some simple + boundary cases, like full green next to full red, + which would generate a covariance matrix like this: + + | 1 -1 0 | + | -1 1 0 | + | 0 0 0 | + + For a given starting vector, the power method can + generate all zeros! So no starting with {1,1,1} + as I was doing! This kind of error is still a + slight posibillity, but will be very rare. + */ + /* use the covariance matrix directly + (1st iteration, don't use all 1.0 values!) */ + sum_r = 1.0f; + sum_g = 2.718281828f; + sum_b = 3.141592654f; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + /* 2nd iteration, use results from the 1st guy */ + sum_r = direction[0]; + sum_g = direction[1]; + sum_b = direction[2]; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + /* 3rd iteration, use results from the 2nd guy */ + sum_r = direction[0]; + sum_g = direction[1]; + sum_b = direction[2]; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + #else + /* use my standard deviation method + (very robust, a tiny bit slower and less accurate) */ + direction[0] = sqrt( sum_rr ); + direction[1] = sqrt( sum_gg ); + direction[2] = sqrt( sum_bb ); + /* which has a greater component */ + if( sum_gg > sum_rr ) + { + /* green has greater component, so base the other signs off of green */ + if( sum_rg < 0.0f ) + { + direction[0] = -direction[0]; + } + if( sum_gb < 0.0f ) + { + direction[2] = -direction[2]; + } + } else + { + /* red has a greater component */ + if( sum_rg < 0.0f ) + { + direction[1] = -direction[1]; + } + if( sum_rb < 0.0f ) + { + direction[2] = -direction[2]; + } + } + #endif +} + +void LSE_master_colors_max_min( + int *cmax, int *cmin, + int channels, + const unsigned char *const uncompressed ) +{ + int i, j; + /* the master colors */ + int c0[3], c1[3]; + /* used for fitting the line */ + float sum_x[] = { 0.0f, 0.0f, 0.0f }; + float sum_x2[] = { 0.0f, 0.0f, 0.0f }; + float dot_max = 1.0f, dot_min = -1.0f; + float vec_len2 = 0.0f; + float dot; + /* error check */ + if( (channels < 3) || (channels > 4) ) + { + return; + } + compute_color_line_STDEV( uncompressed, channels, sum_x, sum_x2 ); + vec_len2 = 1.0f / ( 0.00001f + + sum_x2[0]*sum_x2[0] + sum_x2[1]*sum_x2[1] + sum_x2[2]*sum_x2[2] ); + /* finding the max and min vector values */ + dot_max = + ( + sum_x2[0] * uncompressed[0] + + sum_x2[1] * uncompressed[1] + + sum_x2[2] * uncompressed[2] + ); + dot_min = dot_max; + for( i = 1; i < 16; ++i ) + { + dot = + ( + sum_x2[0] * uncompressed[i*channels+0] + + sum_x2[1] * uncompressed[i*channels+1] + + sum_x2[2] * uncompressed[i*channels+2] + ); + if( dot < dot_min ) + { + dot_min = dot; + } else if( dot > dot_max ) + { + dot_max = dot; + } + } + /* and the offset (from the average location) */ + dot = sum_x2[0]*sum_x[0] + sum_x2[1]*sum_x[1] + sum_x2[2]*sum_x[2]; + dot_min -= dot; + dot_max -= dot; + /* post multiply by the scaling factor */ + dot_min *= vec_len2; + dot_max *= vec_len2; + /* OK, build the master colors */ + for( i = 0; i < 3; ++i ) + { + /* color 0 */ + c0[i] = (int)(0.5f + sum_x[i] + dot_max * sum_x2[i]); + if( c0[i] < 0 ) + { + c0[i] = 0; + } else if( c0[i] > 255 ) + { + c0[i] = 255; + } + /* color 1 */ + c1[i] = (int)(0.5f + sum_x[i] + dot_min * sum_x2[i]); + if( c1[i] < 0 ) + { + c1[i] = 0; + } else if( c1[i] > 255 ) + { + c1[i] = 255; + } + } + /* down_sample (with rounding?) */ + i = rgb_to_565( c0[0], c0[1], c0[2] ); + j = rgb_to_565( c1[0], c1[1], c1[2] ); + if( i > j ) + { + *cmax = i; + *cmin = j; + } else + { + *cmax = j; + *cmin = i; + } +} + +void + compress_DDS_color_block + ( + int channels, + const unsigned char *const uncompressed, + unsigned char compressed[8] + ) +{ + /* variables */ + int i; + int next_bit; + int enc_c0, enc_c1; + int c0[4], c1[4]; + float color_line[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float vec_len2 = 0.0f, dot_offset = 0.0f; + /* stupid order */ + int swizzle4[] = { 0, 2, 3, 1 }; + /* get the master colors */ + LSE_master_colors_max_min( &enc_c0, &enc_c1, channels, uncompressed ); + /* store the 565 color 0 and color 1 */ + compressed[0] = (enc_c0 >> 0) & 255; + compressed[1] = (enc_c0 >> 8) & 255; + compressed[2] = (enc_c1 >> 0) & 255; + compressed[3] = (enc_c1 >> 8) & 255; + /* zero out the compressed data */ + compressed[4] = 0; + compressed[5] = 0; + compressed[6] = 0; + compressed[7] = 0; + /* reconstitute the master color vectors */ + rgb_888_from_565( enc_c0, &c0[0], &c0[1], &c0[2] ); + rgb_888_from_565( enc_c1, &c1[0], &c1[1], &c1[2] ); + /* the new vector */ + vec_len2 = 0.0f; + for( i = 0; i < 3; ++i ) + { + color_line[i] = c1[i] - c0[i]; + vec_len2 += color_line[i] * color_line[i]; + } + if( vec_len2 > 0.0f ) + { + vec_len2 = 1.0f / vec_len2; + } + /* pre-proform the scaling */ + color_line[0] *= vec_len2; + color_line[1] *= vec_len2; + color_line[2] *= vec_len2; + /* compute the offset (constant) portion of the dot product */ + dot_offset = color_line[0]*c0[0] + color_line[1]*c0[1] + color_line[2]*c0[2]; + /* store the rest of the bits */ + next_bit = 8*4; + for( i = 0; i < 16; ++i ) + { + /* find the dot product of this color, to place it on the line + (should be [-1,1]) */ + int next_value = 0; + float dot_product = + color_line[0] * uncompressed[i*channels+0] + + color_line[1] * uncompressed[i*channels+1] + + color_line[2] * uncompressed[i*channels+2] - + dot_offset; + /* map to [0,3] */ + next_value = (int)( dot_product * 3.0f + 0.5f ); + if( next_value > 3 ) + { + next_value = 3; + } else if( next_value < 0 ) + { + next_value = 0; + } + /* OK, store this value */ + compressed[next_bit >> 3] |= swizzle4[ next_value ] << (next_bit & 7); + next_bit += 2; + } + /* done compressing to DXT1 */ +} + +void + compress_DDS_alpha_block + ( + const unsigned char *const uncompressed, + unsigned char compressed[8] + ) +{ + /* variables */ + int i; + int next_bit; + int a0, a1; + float scale_me; + /* stupid order */ + int swizzle8[] = { 1, 7, 6, 5, 4, 3, 2, 0 }; + /* get the alpha limits (a0 > a1) */ + a0 = a1 = uncompressed[3]; + for( i = 4+3; i < 16*4; i += 4 ) + { + if( uncompressed[i] > a0 ) + { + a0 = uncompressed[i]; + } else if( uncompressed[i] < a1 ) + { + a1 = uncompressed[i]; + } + } + /* store those limits, and zero the rest of the compressed dataset */ + compressed[0] = a0; + compressed[1] = a1; + /* zero out the compressed data */ + compressed[2] = 0; + compressed[3] = 0; + compressed[4] = 0; + compressed[5] = 0; + compressed[6] = 0; + compressed[7] = 0; + /* store the all of the alpha values */ + next_bit = 8*2; + scale_me = 7.9999f / (a0 - a1); + for( i = 3; i < 16*4; i += 4 ) + { + /* convert this alpha value to a 3 bit number */ + int svalue; + int value = (int)((uncompressed[i] - a1) * scale_me); + svalue = swizzle8[ value&7 ]; + /* OK, store this value, start with the 1st byte */ + compressed[next_bit >> 3] |= svalue << (next_bit & 7); + if( (next_bit & 7) > 5 ) + { + /* spans 2 bytes, fill in the start of the 2nd byte */ + compressed[1 + (next_bit >> 3)] |= svalue >> (8 - (next_bit & 7) ); + } + next_bit += 3; + } + /* done compressing to DXT1 */ +} diff --git a/src/projectM-engine/image_DXT.h b/src/projectM-engine/image_DXT.h new file mode 100644 index 000000000..ce7716425 --- /dev/null +++ b/src/projectM-engine/image_DXT.h @@ -0,0 +1,123 @@ +/* + Jonathan Dummer + 2007-07-31-10.32 + + simple DXT compression / decompression code + + public domain +*/ + +#ifndef HEADER_IMAGE_DXT +#define HEADER_IMAGE_DXT + +/** + Converts an image from an array of unsigned chars (RGB or RGBA) to + DXT1 or DXT5, then saves the converted image to disk. + \return 0 if failed, otherwise returns 1 +**/ +int +save_image_as_DDS +( + const char *filename, + int width, int height, int channels, + const unsigned char *const data +); + +/** + take an image and convert it to DXT1 (no alpha) +**/ +unsigned char* +convert_image_to_DXT1 +( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size +); + +/** + take an image and convert it to DXT5 (with alpha) +**/ +unsigned char* +convert_image_to_DXT5 +( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size +); + +/** A bunch of DirectDraw Surface structures and flags **/ +typedef struct +{ + unsigned int dwMagic; + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; + unsigned int dwMipMapCount; + unsigned int dwReserved1[ 11 ]; + + /* DDPIXELFORMAT */ + struct + { + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + unsigned int dwRGBBitCount; + unsigned int dwRBitMask; + unsigned int dwGBitMask; + unsigned int dwBBitMask; + unsigned int dwAlphaBitMask; + } + sPixelFormat; + + /* DDCAPS2 */ + struct + { + unsigned int dwCaps1; + unsigned int dwCaps2; + unsigned int dwDDSX; + unsigned int dwReserved; + } + sCaps; + unsigned int dwReserved2; +} +DDS_header ; + +/* the following constants were copied directly off the MSDN website */ + +/* The dwFlags member of the original DDSURFACEDESC2 structure + can be set to one or more of the following values. */ +#define DDSD_CAPS 0x00000001 +#define DDSD_HEIGHT 0x00000002 +#define DDSD_WIDTH 0x00000004 +#define DDSD_PITCH 0x00000008 +#define DDSD_PIXELFORMAT 0x00001000 +#define DDSD_MIPMAPCOUNT 0x00020000 +#define DDSD_LINEARSIZE 0x00080000 +#define DDSD_DEPTH 0x00800000 + +/* DirectDraw Pixel Format */ +#define DDPF_ALPHAPIXELS 0x00000001 +#define DDPF_FOURCC 0x00000004 +#define DDPF_RGB 0x00000040 + +/* The dwCaps1 member of the DDSCAPS2 structure can be + set to one or more of the following values. */ +#define DDSCAPS_COMPLEX 0x00000008 +#define DDSCAPS_TEXTURE 0x00001000 +#define DDSCAPS_MIPMAP 0x00400000 + +/* The dwCaps2 member of the DDSCAPS2 structure can be + set to one or more of the following values. */ +#define DDSCAPS2_CUBEMAP 0x00000200 +#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 +#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 +#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 +#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 +#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 +#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 +#define DDSCAPS2_VOLUME 0x00200000 + +#endif /* HEADER_IMAGE_DXT */ diff --git a/src/projectM-engine/image_helper.c b/src/projectM-engine/image_helper.c new file mode 100644 index 000000000..33ed8104b --- /dev/null +++ b/src/projectM-engine/image_helper.c @@ -0,0 +1,151 @@ +/* + Jonathan Dummer + + image helper functions + + MIT license +*/ + +#include "image_helper.h" +#include + +/* Upscaling the image uses simple bilinear interpolation */ +int + up_scale_image + ( + const unsigned char* const orig, + int width, int height, int channels, + unsigned char* resampled, + int resampled_width, int resampled_height + ) +{ + float y0, dx, dy; + int x, y, c; + + /* error(s) check */ + if ( (width < 1) || (height < 1) || + (resampled_width < 1) || (resampled_height < 1) || + (channels < 1) || + (NULL == orig) || (NULL == resampled) ) + { + /* signify badness */ + return 0; + } + /* + for each given pixel in the new map, find the exact location + from the original map which would contribute to this guy + */ + dx = (float)width / resampled_width; + dy = (float)height / resampled_height; + for ( y = 0; y < resampled_height; ++y ) + { + y0 = (0.5f + y) * dy; + for ( x = 0; x < resampled_width; ++x ) + { + float value; + float samplex = (0.5f + x) * dx; + float sampley = y0; + int intx, inty; + int base_index; + if( samplex < 0.0f ) { samplex = 0.0f; } + if( samplex > width-1.000001f ) { samplex = width-1.000001f; } + if( sampley < 0.0f ) { sampley = 0.0f; } + if( sampley > height-1.000001f ) { sampley = height-1.000001f; } + intx = (int)samplex; + inty = (int)sampley; + samplex -= intx; + sampley -= inty; + base_index = inty * width * channels + intx * channels; + for ( c = 0; c < channels; ++c ) + { + /* do the sampling */ + value = 0.5f; + value += orig[base_index] + *(1.0f-samplex)*(1.0f-sampley); + value += orig[base_index+channels] + *(samplex)*(1.0f-sampley); + value += orig[base_index+width*channels] + *(1.0f-samplex)*(sampley); + value += orig[base_index+width*channels+channels] + *(samplex)*(sampley); + /* move to the next channel */ + ++base_index; + /* save the new value */ + resampled[y*resampled_width*channels+x*channels+c] = + (unsigned char)(value); + } + } + } + /* done */ + return 1; +} + +int + mipmap_image + ( + const unsigned char* const orig, + int width, int height, int channels, + unsigned char* resampled, + int block_size_x, int block_size_y + ) +{ + int mip_width, mip_height; + int i, j, c; + + /* error check */ + if( (width < 1) || (height < 1) || + (channels < 1) || (orig == NULL) || + (resampled == NULL) || + (block_size_x < 1) || (block_size_y < 1) ) + { + /* nothing to do */ + return 0; + } + mip_width = width / block_size_x; + mip_height = height / block_size_y; + if( mip_width < 1 ) + { + mip_width = 1; + } + if( mip_height < 1 ) + { + mip_height = 1; + } + for( j = 0; j < mip_height; ++j ) + { + for( i = 0; i < mip_width; ++i ) + { + for( c = 0; c < channels; ++c ) + { + const int index = (j*block_size_y)*width*channels + (i*block_size_x)*channels + c; + int sum_value; + int u,v; + int u_block = block_size_x; + int v_block = block_size_y; + int block_area; + /* do a bit of checking so we don't over-run the boundaries + (necessary for non-square textures!) */ + if( block_size_x * (i+1) > width ) + { + u_block = width - i*block_size_y; + } + if( block_size_y * (j+1) > height ) + { + v_block = height - j*block_size_y; + } + block_area = u_block*v_block; + /* for this pixel, see what the average + of all the values in the block are. + note: start the sum at the rounding value, not at 0 */ + sum_value = block_area >> 1; + for( v = 0; v < v_block; ++v ) + for( u = 0; u < u_block; ++u ) + { + sum_value += orig[index + v*width*channels + u*channels]; + } + resampled[j*mip_width*channels + i*channels + c] = sum_value / block_area; + } + } + } + return 1; +} diff --git a/src/projectM-engine/image_helper.h b/src/projectM-engine/image_helper.h new file mode 100644 index 000000000..682c068dc --- /dev/null +++ b/src/projectM-engine/image_helper.h @@ -0,0 +1,50 @@ +/* + Jonathan Dummer + + GLFW helper functions + + MIT license +*/ + +#ifndef HEADER_IMAGE_HELPER +#define HEADER_GLFW_HELPER + +#ifdef __cplusplus +extern "C" { +#endif + +/** + This function upscales an image. + Not to be used to create MIPmaps, + but to make it square, + or to make it a power-of-two sized. +**/ +int + up_scale_image + ( + const unsigned char* const orig, + int width, int height, int channels, + unsigned char* resampled, + int resampled_width, int resampled_height + ); + +/** + This function downscales an image. + Used for creating MIPmaps, + the incoming image should be a + power-of-two sized. +**/ +int + mipmap_image + ( + const unsigned char* const orig, + int width, int height, int channels, + unsigned char* resampled, + int block_size_x, int block_size_y + ); + +#ifdef __cplusplus +} +#endif + +#endif /* HEADER_GLFW_HELPER */ diff --git a/src/projectM-engine/pcxLoader.c b/src/projectM-engine/pcxLoader.c deleted file mode 100644 index e982fd70a..000000000 --- a/src/projectM-engine/pcxLoader.c +++ /dev/null @@ -1,349 +0,0 @@ -#include "pcxLoader.h" - -#pragma pack(push,1) - -/* - The pcx Header structure is 128 bytes in size. The information in the structure is roughly - detailed as the following (according to the pcx file format description on www.wotsit.org) - - - Byte Item Size Description/Comments - 0 Manufacturer 1 Constant Flag, 10 = ZSoft .pcx - 1 Version 1 Version information - 0 = Version 2.5 of PC Paintbrush - 2 = Version 2.8 w/palette information - 3 = Version 2.8 w/o palette information - 4 = PC Paintbrush for Windows(Plus for - Windows uses Ver 5) - 5 = Version 3.0 and > of PC Paintbrush - and PC Paintbrush +, includes - Publisher's Paintbrush . Includes - 24-bit .PCX files - 2 Encoding 1 1 = .PCX run length encoding - 3 BitsPerPixel 1 Number of bits to represent a pixel - (per Plane) - 1, 2, 4, or 8 - 4 Window 8 Image Dimensions: Xmin,Ymin,Xmax,Ymax - 12 HDpi 2 Horizontal Resolution of image in DPI* - 14 VDpi 2 Vertical Resolution of image in DPI* - 16 Colormap 48 Color palette setting, see text - 64 Reserved 1 Should be set to 0. - 65 NPlanes 1 Number of color planes - 66 BytesPerLine 2 Number of bytes to allocate for a scanline - plane. MUST be an EVEN number. Do NOT - calculate from Xmax-Xmin. - 68 PaletteInfo 2 How to interpret palette- 1 = Color/BW, - 2 = Grayscale (ignored in PB IV/ IV +) - 70 HscreenSize 2 Horizontal screen size in pixels. New field - found only in PB IV/IV Plus - 72 VscreenSize 2 Vertical screen size in pixels. New field - found only in PB IV/IV Plus - 74 Filler 54 Blank to fill out 128 byte header. Set all - bytes to 0 - - - This then starts to look somewhat like this : -*/ -typedef struct -{ - unsigned char Manufacturer; - unsigned char Version; - unsigned char Encoding; - unsigned char BitsPerPixel; - - struct - { - unsigned short Xmin; - unsigned short Ymin; - unsigned short Xmax; - unsigned short Ymax; - } - Window; - - short HDpi; - short VDpi; - unsigned char ColorMap[48]; - unsigned char Reserved1; - unsigned char NPlanes; - short BytesPerLine; - short PaletteInfo; - short HscreenSize; - short VscreenSize; - unsigned char Filler[54]; -} -PcxHeader; - -/*! - * \brief This structure is intended to hold a single byte of information from the pcx file. This is kinda important - * because the bytes hold information about whether a run or single byte is used. This is basically a bit - * annoying so instead it is easier to create something using bit-fields that can quickly identify the bit - * flags that are set rather than using horrible bit-wise operations. I just kinda prefer it cos it's more - * readable.... - */ -typedef struct -{ - union - { - struct - { - unsigned char Count : 6; - /* if these two bits are 1, then we have a start of a byte run */ - unsigned char Flag1 : 1; - unsigned char Flag2 : 1; - }; - unsigned char byte; - }; -} -PcxByte; - -typedef struct -{ - unsigned char r; - unsigned char g; - unsigned char b; -} -PcxColour; - -#pragma pack(pop) - - -#include -#include -#include - - - -/*! - * \brief This function loads a ZSoft PCX File image file. - * \param filename - the name of the file to load - * \param pixels - pointer to the pointer that will hold the pixel data (yuck) - * \param w - the width of the image file - * \param h - the height of the image file - * \param bpp - the bytes per pixel of the image file. - * \return 1 if ok, 0 otherwise - */ -int LoadPcxImage( const char filename[], - unsigned char** pixels, - unsigned int* w, - unsigned int* h, - unsigned int* bpp) -{ - /* open the file */ - FILE* fp = fopen( filename,"rb" ); - - /* if opened OK */ - if(fp) - { - /* holds the 128 byte header */ - PcxHeader header; - - /* read the header in */ - fread(&header,128,1,fp); - - if( header.Manufacturer == 10 ) - { - /* calculate the image width & height */ - unsigned int XSIZE = header.Window.Xmax - header.Window.Xmin + 1; - unsigned int YSIZE = header.Window.Ymax - header.Window.Ymin + 1; - - /* The number of bytes per line */ - unsigned int BytesPerLine = header.BytesPerLine * header.NPlanes; - - /* simple count vars */ - unsigned int i,j,k; - - /* a temporary buffer for a scanline */ - unsigned char *tempbuffer; - - /* another temp var */ - unsigned int iCount; - - - - /* check pcx version */ - if( header.Version != 0x05 ) - { - printf("[ERROR] Only version 5 of the pcx file format is supported....\n"); - fclose(fp); - return 0; - } - - /* allocate enough space for the pixel data */ - tempbuffer = (unsigned char*) malloc ( BytesPerLine ); - assert( tempbuffer ); - - - /* allocate enough space for the pixel data */ - (*pixels) = (unsigned char*) malloc ( header.NPlanes * XSIZE * YSIZE ); - assert( *pixels ); - - /* store info */ - *w = XSIZE; - *h = YSIZE; - *bpp = header.NPlanes; - - /* Loop through until whole image is loaded.... */ - for (i = 0; i < YSIZE; ++i ) - { - j = 0; - - /* - * because the scanlines are orderer RRRGGGBBB rather than RGBRGBRGB, I'm reading and decompressing - * the file one scanline at a time. - */ - while( j < BytesPerLine ) - { - PcxByte byte; - - /* assume that at the very least we have a count of 1 */ - iCount=1; - - /* read a byte */ - fread( &byte, 1, 1, fp ); - - /* - * If the top 2 bits of the byte are 1, then the lower 6 bits represent a data count. - * The following byte will then be repeated 'count' number of times. - */ - if( byte.Flag1 && byte.Flag2 ) - { - /* see how many byte repeats we have */ - iCount = (unsigned int)byte.Count; - - /* read the data byte */ - fread( &byte, 1, 1, fp ); - } - - /* expand the compressed data... */ - for ( k = 0; k < iCount; ++k ) - { - tempbuffer[j++] = byte.byte; - } - } - - /* having read the scanline into the scanline buffer, now re-organise into RGB data in the pixel buffer */ - for( j=0; j < XSIZE; ++j ) - { - unsigned char *scanline = ((*pixels) + (XSIZE * header.NPlanes * (YSIZE-i-1))); - - for( k =0; k < header.NPlanes; ++k ) - { - scanline[ (j * header.NPlanes) + k ] = tempbuffer[ (header.BytesPerLine * k) + j ]; - } - } - } - - /* make sure we get rid of the temporary buffer memory allocation.... */ - free( tempbuffer ); - - /* if the bytes per pixel == 1, then there will be a colourmap at the end (hopefully) */ - if( header.NPlanes == 1 ) - { - unsigned char testchar=0; - - /* move to 769 bytes from the end... */ - fseek( fp, -769, SEEK_END ); - - /* read the next char */ - testchar=fgetc(fp); - - /* hopefully it will be 12, or 0x0A which indicates the presence of a colour map */ - if( testchar == 0x0C ) - { - /* allocate enough memory for the colour map */ - PcxColour* Colourmap = (PcxColour*) malloc ( 768 ); - assert(Colourmap); - - /* read the colour map data */ - fread( Colourmap, sizeof(PcxColour), 256, fp ); - - /* see notes below/... */ - testchar = 0x01; - - /* - * this is a really crap check for greyscale images. As far as I can tell, a pcx file stores - * greyscale images by storing the grey tones in the colour palette. I can't really see the point - * of expanding this to 24bit because it will then work differently to the other image loaders. - * as a result I check to see if all colours read in actually match their index in which case - * I will just ignore the colour pallette info.... - */ - for(i=0;i<256;++i) - { - if( Colourmap[i].r != i || - Colourmap[i].g != i || - Colourmap[i].b != i ) - { - testchar = 0; - break; - } - } - - /* if the test proved false, then I will expand the data out into a 24bit colour image */ - if( testchar == 0x00 ) - { - unsigned char* px_o= (*pixels); - unsigned char* end_o = px_o + (XSIZE*YSIZE); - unsigned char* px; - - tempbuffer = (unsigned char*) malloc( XSIZE*YSIZE*3 ); - assert(tempbuffer); - - px = tempbuffer; - - /* Expand the old 8bit colourmapped data into 24bit... */ - while( px_o != end_o ) - { - *px = Colourmap[ *px_o ].r; - ++px; - - *px = Colourmap[ *px_o ].g; - ++px; - - *px = Colourmap[ *px_o ].b; - ++px; - - ++px_o; - } - - /* delete the not needed 8bit data */ - free( *pixels ); - - /* store pointer to new pixel data */ - *pixels = tempbuffer; - - /* update the bytes per pixel... */ - *bpp = 3; - } - - free( Colourmap ); - } - } - - } - else - { - printf("[ERROR] %s is not a pcx file!!\n" , filename ); - fclose(fp); - return 0; - } - - fclose(fp); - - return 1; - } - return 0; -} - - - - - - - - - - - - - - - diff --git a/src/projectM-engine/pcxLoader.h b/src/projectM-engine/pcxLoader.h deleted file mode 100644 index 6d0f90ee8..000000000 --- a/src/projectM-engine/pcxLoader.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ROBS_PCX_LOADER___H_ -#define ROBS_PCX_LOADER___H_ - - -#ifdef __cplusplus -extern "C" { -#endif - - - -/*! - * \brief This function loads a ZSoft PCX File image file. - * \param filename - the name of the file to load - * \param pixels - pointer to the pointer that will hold the pixel data (yuck) - * \param w - the width of the image file - * \param h - the height of the image file - * \param bpp - the bytes per pixel of the image file. - * \return 1 if ok, 0 otherwise - */ -int LoadPcxImage( const char filename[], - unsigned char** pixels, - unsigned int* w, - unsigned int* h, - unsigned int* bpp); - - -#ifdef __cplusplus -} -#endif - - -#endif - diff --git a/src/projectM-engine/stb_image.c b/src/projectM-engine/stb_image.c new file mode 100644 index 000000000..bf20190dc --- /dev/null +++ b/src/projectM-engine/stb_image.c @@ -0,0 +1,3283 @@ +/* stbi-1.00 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c + when you control the images you're loading + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline (no JPEG progressive, no oddball channel decimations) + PNG non-interlaced + BMP non-1bpp, non-RLE + writes BMP,TGA (define STBI_NO_WRITE to remove code) + decoded from memory or through stdio FILE (define STBI_NO_STDIO to remove code) + + TODO: + stbi_info_* + PSD loader + + history: + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less + than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant +*/ + +#include "stb_image.h" + +#ifndef STBI_NO_STDIO +#include +#endif +#include +#include +#include +#include + +#ifndef _MSC_VER +#define __forceinline +#endif + +// implementation: +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; +typedef unsigned int uint; + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(uint32)==4]; + +#if defined(STBI_NO_STDIO) && !defined(STBI_NO_WRITE) +#define STBI_NO_WRITE +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Generic API that works on all image types +// + +// I (JLD) want full messages for SOIL +#define STBI_FAILURE_USERMSG 1 + +static char *failure_reason; + +char *stbi_failure_reason(void) +{ + return failure_reason; +} + +static int e(char *str) +{ + failure_reason = str; + return 0; +} + +#ifdef STBI_NO_FAILURE_STRINGS + #define e(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define e(x,y) e(y) +#else + #define e(x,y) e(x) +#endif + +#define ep(x,y) (e(x,y),NULL) + +void stbi_image_free(unsigned char *retval_from_stbi_load) +{ + free(retval_from_stbi_load); +} + +#define MAX_LOADERS 32 +stbi_loader *loaders[MAX_LOADERS]; +static int max_loaders = 0; + +int stbi_register_loader(stbi_loader *loader) +{ + int i; + for (i=0; i < MAX_LOADERS; ++i) { + // already present? + if (loaders[i] == loader) + return 1; + // end of the list? + if (loaders[i] == NULL) { + loaders[i] = loader; + max_loaders = i+1; + return 1; + } + } + // no room for it + return 0; +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_load(char *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + unsigned char *result; + if (!f) return ep("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + int i; + if (stbi_jpeg_test_file(f)) + return stbi_jpeg_load_from_file(f,x,y,comp,req_comp); + if (stbi_png_test_file(f)) + return stbi_png_load_from_file(f,x,y,comp,req_comp); + if (stbi_bmp_test_file(f)) + return stbi_bmp_load_from_file(f,x,y,comp,req_comp); + if (stbi_dds_test_file(f)) + return stbi_dds_load_from_file(f,x,y,comp,req_comp); + if (stbi_tga_test_file(f)) + return stbi_tga_load_from_file(f,x,y,comp,req_comp); + for (i=0; i < max_loaders; ++i) + if (loaders[i]->test_file(f)) + return loaders[i]->load_from_file(f,x,y,comp,req_comp); + return ep("unknown image type", "Image not of any known type, or corrupt"); +} +#endif + +unsigned char *stbi_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + int i; + if (stbi_jpeg_test_memory(buffer,len)) + return stbi_jpeg_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_png_test_memory(buffer,len)) + return stbi_png_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_bmp_test_memory(buffer,len)) + return stbi_bmp_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_dds_test_memory(buffer,len)) + return stbi_dds_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_tga_test_memory(buffer,len)) + return stbi_tga_load_from_memory(buffer,len,x,y,comp,req_comp); + for (i=0; i < max_loaders; ++i) + if (loaders[i]->test_memory(buffer,len)) + return loaders[i]->load_from_memory(buffer,len,x,y,comp,req_comp); + return ep("unknown image type", "Image not of any known type, or corrupt"); +} + +// @TODO: get image dimensions & components without fully decoding +extern int stbi_info (char *filename, int *x, int *y, int *comp); +extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +extern int stbi_info_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp); + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +// image width, height, # components +static uint32 img_x, img_y; +static int img_n, img_out_n; + +enum +{ + SCAN_load=0, + SCAN_type, + SCAN_header, +}; + +// An API for reading either from memory or file. +// It fits on a single screen. No abstract base classes needed. +#ifndef STBI_NO_STDIO +static FILE *img_file; +#endif +static uint8 *img_buffer, *img_buffer_end; + +#ifndef STBI_NO_STDIO +static void start_file(FILE *f) +{ + img_file = f; +} +#endif + +static void start_mem(uint8 *buffer, int len) +{ +#ifndef STBI_NO_STDIO + img_file = NULL; +#endif + img_buffer = buffer; + img_buffer_end = buffer+len; +} + +static int get8(void) +{ +#ifndef STBI_NO_STDIO + if (img_file) { + int c = fgetc(img_file); + return c == EOF ? 0 : c; + } +#endif + if (img_buffer < img_buffer_end) + return *img_buffer++; + return 0; +} + +static int at_eof(void) +{ +#ifndef STBI_NO_STDIO + if (img_file) + return feof(img_file); +#endif + return img_buffer >= img_buffer_end; +} + +static uint8 get8u(void) +{ + return (uint8) get8(); +} + +static void skip(int n) +{ +#ifndef STBI_NO_STDIO + if (img_file) + fseek(img_file, n, SEEK_CUR); + else +#endif + img_buffer += n; +} + +static int get16(void) +{ + int z = get8(); + return (z << 8) + get8(); +} + +static uint32 get32(void) +{ + uint32 z = get16(); + return (z << 16) + get16(); +} + +static int get16le(void) +{ + int z = get8(); + return z + (get8() << 8); +} + +static uint32 get32le(void) +{ + uint32 z = get16le(); + return z + (get16le() << 16); +} + +static void getn(stbi_uc *buffer, int n) +{ +#ifndef STBI_NO_STDIO + if (img_file) { + fread(buffer, 1, n, img_file); + return; + } +#endif + memcpy(buffer, img_buffer, n); + img_buffer += n; +} + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static uint8 compute_y(int r, int g, int b) +{ + return (uint8) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *convert_format(unsigned char *data, int img_n, int req_comp) +{ + uint i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + assert(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) malloc(req_comp * img_x * img_y); + if (good == NULL) { + free(data); + return ep("outofmem", "Out of memory"); + } + + for (j=0; j < img_y; ++j) { + unsigned char *src = data + j * img_x * img_n ; + unsigned char *dest = good + j * img_x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=0; i < img_x; ++i, src += a, dest += b) + + // convert source image with img_n components to one with req_comp components + switch(COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: assert(0); + } + #undef CASE + } + + free(data); + img_out_n = req_comp; + return good; +} + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) +// +// simple implementation +// - channel subsampling of at most 2 in each dimension +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - uses a lot of intermediate memory, could cache poorly +// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 +// stb_jpeg: 1.34 seconds (MSVC6, default release build) +// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) +// IJL11.dll: 1.08 seconds (compiled by intel) +// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) +// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) + +int stbi_jpeg_dc_only; + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + uint8 fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + uint16 code[256]; + uint8 values[256]; + uint8 size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} huffman; + +static huffman huff_dc[4]; // baseline is 2 tables, extended is 4 +static huffman huff_ac[4]; +static uint8 dequant[4][64]; + +static int build_huffman(huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (uint8) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (uint16) (code++); + if (code-1 >= (1 << j)) return e("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (uint8) i; + } + } + } + return 1; +} + +// sizes for components, interleaved MCUs +static int img_h_max, img_v_max; +static int img_mcu_x, img_mcu_y; +static int img_mcu_w, img_mcu_h; + +// definition of jpeg image component +static struct +{ + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + uint8 *data; +} img_comp[4]; + +static unsigned long code_buffer; // jpeg entropy-coded buffer +static int code_bits; // number of valid bits +static unsigned char marker; // marker seen while filling entropy buffer +static int nomore; // flag if we saw a marker so must stop + +static void grow_buffer_unsafe(void) +{ + do { + int b = nomore ? 0 : get8(); + if (b == 0xff) { + int c = get8(); + if (c != 0) { + marker = (unsigned char) c; + nomore = 1; + return; + } + } + code_buffer = (code_buffer << 8) | b; + code_bits += 8; + } while (code_bits <= 24); +} + +// (1 << n) - 1 +static unsigned long bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +__forceinline static int decode(huffman *h) +{ + unsigned int temp; + int c,k; + + if (code_bits < 16) grow_buffer_unsafe(); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (code_buffer >> (code_bits - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + if (h->size[k] > code_bits) + return -1; + code_bits -= h->size[k]; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + if (code_bits < 16) + temp = (code_buffer << (16 - code_bits)) & 0xffff; + else + temp = (code_buffer >> (code_bits - 16)) & 0xffff; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + code_bits -= 16; + return -1; + } + + if (k > code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((code_buffer >> (code_bits - k)) & bmask[k]) + h->delta[k]; + assert((((code_buffer) >> (code_bits - h->size[c])) & bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + code_bits -= k; + return h->values[c]; +} + +// combined JPEG 'receive' and JPEG 'extend', since baseline +// always extends everything it receives. +__forceinline static int extend_receive(int n) +{ + unsigned int m = 1 << (n-1); + unsigned int k; + if (code_bits < n) grow_buffer_unsafe(); + k = (code_buffer >> (code_bits - n)) & bmask[n]; + code_bits -= n; + // the following test is probably a random branch that won't + // predict well. I tried to table accelerate it but failed. + // maybe it's compiling as a conditional move? + if (k < m) + return (-1 << n) + k + 1; + else + return k; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static uint8 dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int decode_block(short data[64], huffman *hdc, huffman *hac, int b) +{ + int diff,dc,k; + int t = decode(hdc); + if (t < 0) return e("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? extend_receive(t) : 0; + dc = img_comp[b].dc_pred + diff; + img_comp[b].dc_pred = dc; + data[0] = (short) dc; + + // decode AC components, see JPEG spec + k = 1; + do { + int r,s; + int rs = decode(hac); + if (rs < 0) return e("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + data[dezigzag[k++]] = (short) extend_receive(s); + } + } while (k < 64); + return 1; +} + +// take a -128..127 value and clamp it and convert to 0..255 +__forceinline static uint8 clamp(int x) +{ + x += 128; + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (uint8) x; +} + +#define f2f(x) (int) (((x) * 4096 + 0.5)) +#define fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * f2f(0.5411961f); \ + t2 = p1 + p3*f2f(-1.847759065f); \ + t3 = p1 + p2*f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = fsh(p2+p3); \ + t1 = fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*f2f( 1.175875602f); \ + t0 = t0*f2f( 0.298631336f); \ + t1 = t1*f2f( 2.053119869f); \ + t2 = t2*f2f( 3.072711026f); \ + t3 = t3*f2f( 1.501321110f); \ + p1 = p5 + p1*f2f(-0.899976223f); \ + p2 = p5 + p2*f2f(-2.562915447f); \ + p3 = p3*f2f(-1.961570560f); \ + p4 = p4*f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +// .344 seconds on 3*anemones.jpg +static void idct_block(uint8 *out, int out_stride, short data[64], uint8 *dequantize) +{ + int i,val[64],*v=val; + uint8 *o,*dq = dequantize; + short *d = data; + + if (stbi_jpeg_dc_only) { + // ok, I don't really know why this is right, but it seems to be: + int z = 128 + ((d[0] * dq[0]) >> 3); + for (i=0; i < 8; ++i) { + out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] = out[7] = z; + out += out_stride; + } + return; + } + + // columns + for (i=0; i < 8; ++i,++d,++dq, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] * dq[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], + d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + x0 += 65536; x1 += 65536; x2 += 65536; x3 += 65536; + o[0] = clamp((x0+t3) >> 17); + o[7] = clamp((x0-t3) >> 17); + o[1] = clamp((x1+t2) >> 17); + o[6] = clamp((x1-t2) >> 17); + o[2] = clamp((x2+t1) >> 17); + o[5] = clamp((x2-t1) >> 17); + o[3] = clamp((x3+t0) >> 17); + o[4] = clamp((x3-t0) >> 17); + } +} + +#define MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static uint8 get_marker(void) +{ + uint8 x; + if (marker != MARKER_none) { x = marker; marker = MARKER_none; return x; } + x = get8u(); + if (x != 0xff) return MARKER_none; + while (x == 0xff) + x = get8u(); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +static int scan_n, order[4]; +static int restart_interval, todo; +#define RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, reset the entropy decoder and +// the dc prediction +static void reset(void) +{ + code_bits = 0; + code_buffer = 0; + nomore = 0; + img_comp[0].dc_pred = img_comp[1].dc_pred = img_comp[2].dc_pred = 0; + marker = MARKER_none; + todo = restart_interval ? restart_interval : 0x7fffffff; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int parse_entropy_coded_data(void) +{ + reset(); + if (scan_n == 1) { + int i,j; + short data[64]; + int n = order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (img_comp[n].x+7) >> 3; + int h = (img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + if (!decode_block(data, huff_dc+img_comp[n].hd, huff_ac+img_comp[n].ha, n)) return 0; + idct_block(img_comp[n].data+img_comp[n].w2*j*8+i*8, img_comp[n].w2, data, dequant[img_comp[n].tq]); + // every data block is an MCU, so countdown the restart interval + if (--todo <= 0) { + if (code_bits < 24) grow_buffer_unsafe(); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(marker)) return 1; + reset(); + } + } + } + } else { // interleaved! + int i,j,k,x,y; + short data[64]; + for (j=0; j < img_mcu_y; ++j) { + for (i=0; i < img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < scan_n; ++k) { + int n = order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < img_comp[n].v; ++y) { + for (x=0; x < img_comp[n].h; ++x) { + int x2 = (i*img_comp[n].h + x)*8; + int y2 = (j*img_comp[n].v + y)*8; + if (!decode_block(data, huff_dc+img_comp[n].hd, huff_ac+img_comp[n].ha, n)) return 0; + idct_block(img_comp[n].data+img_comp[n].w2*y2+x2, img_comp[n].w2, data, dequant[img_comp[n].tq]); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--todo <= 0) { + if (code_bits < 24) grow_buffer_unsafe(); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(marker)) return 1; + reset(); + } + } + } + } + return 1; +} + +static int process_marker(int m) +{ + int L; + switch (m) { + case MARKER_none: // no marker found + return e("expected marker","Corrupt JPEG"); + + case 0xC2: // SOF - progressive + return e("progressive jpeg","JPEG format not supported (progressive)"); + + case 0xDD: // DRI - specify restart interval + if (get16() != 4) return e("bad DRI len","Corrupt JPEG"); + restart_interval = get16(); + return 1; + + case 0xDB: // DQT - define quantization table + L = get16()-2; + while (L > 0) { + int z = get8(); + int p = z >> 4; + int t = z & 15,i; + if (p != 0) return e("bad DQT type","Corrupt JPEG"); + if (t > 3) return e("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + dequant[t][dezigzag[i]] = get8u(); + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = get16()-2; + while (L > 0) { + uint8 *v; + int sizes[16],i,m=0; + int z = get8(); + int tc = z >> 4; + int th = z & 15; + if (tc > 1 || th > 3) return e("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = get8(); + m += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!build_huffman(huff_dc+th, sizes)) return 0; + v = huff_dc[th].values; + } else { + if (!build_huffman(huff_ac+th, sizes)) return 0; + v = huff_ac[th].values; + } + for (i=0; i < m; ++i) + v[i] = get8u(); + L -= m; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + skip(get16()-2); + return 1; + } + return 0; +} + +// after we see SOS +static int process_scan_header(void) +{ + int i; + int Ls = get16(); + scan_n = get8(); + if (scan_n < 1 || scan_n > 4 || scan_n > (int) img_n) return e("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*scan_n) return e("bad SOS len","Corrupt JPEG"); + for (i=0; i < scan_n; ++i) { + int id = get8(), which; + int z = get8(); + for (which = 0; which < img_n; ++which) + if (img_comp[which].id == id) + break; + if (which == img_n) return 0; + img_comp[which].hd = z >> 4; if (img_comp[which].hd > 3) return e("bad DC huff","Corrupt JPEG"); + img_comp[which].ha = z & 15; if (img_comp[which].ha > 3) return e("bad AC huff","Corrupt JPEG"); + order[i] = which; + } + if (get8() != 0) return e("bad SOS","Corrupt JPEG"); + get8(); // should be 63, but might be 0 + if (get8() != 0) return e("bad SOS","Corrupt JPEG"); + + return 1; +} + +static int process_frame_header(int scan) +{ + int Lf,p,i,z, h_max=1,v_max=1; + Lf = get16(); if (Lf < 11) return e("bad SOF len","Corrupt JPEG"); // JPEG + p = get8(); if (p != 8) return e("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + img_y = get16(); if (img_y == 0) return e("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + img_x = get16(); if (img_x == 0) return e("0 width","Corrupt JPEG"); // JPEG requires + img_n = get8(); + if (img_n != 3 && img_n != 1) return e("bad component count","Corrupt JPEG"); // JFIF requires + + if (Lf != 8+3*img_n) return e("bad SOF len","Corrupt JPEG"); + + for (i=0; i < img_n; ++i) { + img_comp[i].id = get8(); + if (img_comp[i].id != i+1) // JFIF requires + if (img_comp[i].id != i) // jpegtran outputs non-JFIF-compliant files! + return e("bad component ID","Corrupt JPEG"); + z = get8(); + img_comp[i].h = (z >> 4); if (!img_comp[i].h || img_comp[i].h > 4) return e("bad H","Corrupt JPEG"); + img_comp[i].v = z & 15; if (!img_comp[i].v || img_comp[i].v > 4) return e("bad V","Corrupt JPEG"); + img_comp[i].tq = get8(); if (img_comp[i].tq > 3) return e("bad TQ","Corrupt JPEG"); + } + + if (scan != SCAN_load) return 1; + + if ((1 << 30) / img_x / img_n < img_y) return e("too large", "Image too large to decode"); + + for (i=0; i < img_n; ++i) { + if (img_comp[i].h > h_max) h_max = img_comp[i].h; + if (img_comp[i].v > v_max) v_max = img_comp[i].v; + } + + // compute interleaved mcu info + img_h_max = h_max; + img_v_max = v_max; + img_mcu_w = h_max * 8; + img_mcu_h = v_max * 8; + img_mcu_x = (img_x + img_mcu_w-1) / img_mcu_w; + img_mcu_y = (img_y + img_mcu_h-1) / img_mcu_h; + + for (i=0; i < img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + img_comp[i].x = (img_x * img_comp[i].h + h_max-1) / h_max; + img_comp[i].y = (img_y * img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + img_comp[i].w2 = img_mcu_x * img_comp[i].h * 8; + img_comp[i].h2 = img_mcu_y * img_comp[i].v * 8; + img_comp[i].data = (uint8 *) malloc(img_comp[i].w2 * img_comp[i].h2); + if (img_comp[i].data == NULL) { + for(--i; i >= 0; --i) + free(img_comp[i].data); + return e("outofmem", "Out of memory"); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define DNL(x) ((x) == 0xdc) +#define SOI(x) ((x) == 0xd8) +#define EOI(x) ((x) == 0xd9) +#define SOF(x) ((x) == 0xc0 || (x) == 0xc1) +#define SOS(x) ((x) == 0xda) + +static int decode_jpeg_header(int scan) +{ + int m; + marker = MARKER_none; // initialize cached marker to empty + m = get_marker(); + if (!SOI(m)) return e("no SOI","Corrupt JPEG"); + if (scan == SCAN_type) return 1; + m = get_marker(); + while (!SOF(m)) { + if (!process_marker(m)) return 0; + m = get_marker(); + while (m == MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (at_eof()) return e("no SOF", "Corrupt JPEG"); + m = get_marker(); + } + } + if (!process_frame_header(scan)) return 0; + return 1; +} + +static int decode_jpeg_image(void) +{ + int m; + restart_interval = 0; + if (!decode_jpeg_header(SCAN_load)) return 0; + m = get_marker(); + while (!EOI(m)) { + if (SOS(m)) { + if (!process_scan_header()) return 0; + if (!parse_entropy_coded_data()) return 0; + } else { + if (!process_marker(m)) return 0; + } + m = get_marker(); + } + return 1; +} + +// static jfif-centered resampling with cross-block smoothing +// here by cross-block smoothing what I mean is that the resampling +// is bilerp and crosses blocks; I dunno what IJG means + +#define div4(x) ((uint8) ((x) >> 2)) + +static void resample_v_2(uint8 *out1, uint8 *input, int w, int h, int s) +{ + // need to generate two samples vertically for every one in input + uint8 *above; + uint8 *below; + uint8 *source; + uint8 *out2; + int i,j; + source = input; + out2 = out1+w; + for (j=0; j < h; ++j) { + above = source; + source = input + j*s; + below = source + s; if (j == h-1) below = source; + for (i=0; i < w; ++i) { + int n = source[i]*3; + out1[i] = div4(above[i] + n); + out2[i] = div4(below[i] + n); + } + out1 += w*2; + out2 += w*2; + } +} + +static void resample_h_2(uint8 *out, uint8 *input, int w, int h, int s) +{ + // need to generate two samples horizontally for every one in input + int i,j; + if (w == 1) { + for (j=0; j < h; ++j) + out[j*2+0] = out[j*2+1] = input[j*s]; + return; + } + for (j=0; j < h; ++j) { + out[0] = input[0]; + out[1] = div4(input[0]*3 + input[1]); + for (i=1; i < w-1; ++i) { + int n = input[i]*3; + out[i*2-2] = div4(input[i-1] + n); + out[i*2-1] = div4(input[i+1] + n); + } + out[w*2-2] = div4(input[w-2]*3 + input[w-1]); + out[w*2-1] = input[w-1]; + out += w*2; + input += s; + } +} + +// .172 seconds on 3*anemones.jpg +static void resample_hv_2(uint8 *out, uint8 *input, int w, int h, int s) +{ + // need to generate 2x2 samples for every one in input + int i,j; + int os = w*2; + // generate edge samples... @TODO lerp them! + for (i=0; i < w; ++i) { + out[i*2+0] = out[i*2+1] = input[i]; + out[i*2+(2*h-1)*os+0] = out[i*2+(2*h-1)*os+1] = input[i+(h-1)*w]; + } + for (j=0; j < h; ++j) { + out[j*os*2+0] = out[j*os*2+os+0] = input[j*w]; + out[j*os*2+os-1] = out[j*os*2+os+os-1] = input[j*w+i-1]; + } + // now generate interior samples; i & j point to top left of input + for (j=0; j < h-1; ++j) { + uint8 *in1 = input+j*s; + uint8 *in2 = in1 + s; + uint8 *out1 = out + (j*2+1)*os + 1; + uint8 *out2 = out1 + os; + for (i=0; i < w-1; ++i) { + int p00 = in1[0], p01=in1[1], p10=in2[0], p11=in2[1]; + int p00_3 = p00*3, p01_3 = p01*3, p10_3 = p10*3, p11_3 = p11*3; + + #define div16(x) ((uint8) ((x) >> 4)) + + out1[0] = div16(p00*9 + p01_3 + p10_3 + p11); + out1[1] = div16(p01*9 + p00_3 + p01_3 + p10); + out2[0] = div16(p10*9 + p11_3 + p00_3 + p01); + out2[1] = div16(p11*9 + p10_3 + p01_3 + p00); + out1 += 2; + out2 += 2; + ++in1; + ++in2; + } + } +} + +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) + +// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) +// VC6 without processor=Pro is generating multiple LEAs per multiply! +static void YCbCr_to_RGB_row(uint8 *out, uint8 *y, uint8 *pcb, uint8 *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (uint8)r; + out[1] = (uint8)g; + out[2] = (uint8)b; + if (step == 4) out[3] = 255; + out += step; + } +} + +// clean up the temporary component buffers +static void cleanup_jpeg(void) +{ + int i; + for (i=0; i < img_n; ++i) { + if (img_comp[i].data) { + free(img_comp[i].data); + img_comp[i].data = NULL; + } + } +} + +static uint8 *load_jpeg_image(int *out_x, int *out_y, int *comp, int req_comp) +{ + int i, n; + // validate req_comp + if (req_comp < 0 || req_comp > 4) return ep("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source + if (!decode_jpeg_image()) { cleanup_jpeg(); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : img_n; + + // resample components to full size... memory wasteful, but this + // lets us bilerp across blocks while upsampling + for (i=0; i < img_n; ++i) { + // if we're outputting fewer than 3 components, we're grey not RGB; + // in that case, don't bother upsampling Cb or Cr + if (n < 3 && i) continue; + + // check if the component scale is less than max; if so it needs upsampling + if (img_comp[i].h != img_h_max || img_comp[i].v != img_v_max) { + int stride = img_x; + // allocate final size; make sure it's big enough for upsampling off + // the edges with upsample up to 4x4 (although we only support 2x2 + // currently) + uint8 *new_data = (uint8 *) malloc((img_x+3)*(img_y+3)); + if (new_data == NULL) { + cleanup_jpeg(); + return ep("outofmem", "Out of memory (image too large?)"); + } + if (img_comp[i].h*2 == img_h_max && img_comp[i].v*2 == img_v_max) { + int tx = (img_x+1)>>1; + resample_hv_2(new_data, img_comp[i].data, tx,(img_y+1)>>1, img_comp[i].w2); + stride = tx*2; + } else if (img_comp[i].h == img_h_max && img_comp[i].v*2 == img_v_max) { + resample_v_2(new_data, img_comp[i].data, img_x,(img_y+1)>>1, img_comp[i].w2); + } else if (img_comp[i].h*2 == img_h_max && img_comp[i].v == img_v_max) { + int tx = (img_x+1)>>1; + resample_h_2(new_data, img_comp[i].data, tx,img_y, img_comp[i].w2); + stride = tx*2; + } else { + // @TODO resample uncommon sampling pattern with nearest neighbor + free(new_data); + cleanup_jpeg(); + return ep("uncommon H or V", "JPEG not supported: atypical downsampling mode"); + } + img_comp[i].w2 = stride; + free(img_comp[i].data); + img_comp[i].data = new_data; + } + } + + // now convert components to output image + { + uint32 i,j; + uint8 *output = (uint8 *) malloc(n * img_x * img_y + 1); + if (n >= 3) { // output STBI_rgb_* + for (j=0; j < img_y; ++j) { + uint8 *y = img_comp[0].data + j*img_comp[0].w2; + uint8 *out = output + n * img_x * j; + if (img_n == 3) { + uint8 *cb = img_comp[1].data + j*img_comp[1].w2; + uint8 *cr = img_comp[2].data + j*img_comp[2].w2; + YCbCr_to_RGB_row(out, y, cb, cr, img_x, n); + } else { + for (i=0; i < img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n == 3 + out += n; + } + } + } + } else { // output STBI_grey_* + for (j=0; j < img_y; ++j) { + uint8 *y = img_comp[0].data + j*img_comp[0].w2; + uint8 *out = output + n * img_x * j; + if (n == 1) + for (i=0; i < img_x; ++i) *out++ = *y++; + else + for (i=0; i < img_x; ++i) *out++ = *y++, *out++ = 255; + } + } + cleanup_jpeg(); + *out_x = img_x; + *out_y = img_y; + if (comp) *comp = img_n; // report original components, not output + return output; + } +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_jpeg_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + start_file(f); + return load_jpeg_image(x,y,comp,req_comp); +} + +unsigned char *stbi_jpeg_load(char *filename, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_jpeg_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +unsigned char *stbi_jpeg_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + start_mem(buffer,len); + return load_jpeg_image(x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +int stbi_jpeg_test_file(FILE *f) +{ + int n,r; + n = ftell(f); + start_file(f); + r = decode_jpeg_header(SCAN_type); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_jpeg_test_memory(unsigned char *buffer, int len) +{ + start_mem(buffer,len); + return decode_jpeg_header(SCAN_type); +} + +// @TODO: +extern int stbi_jpeg_info (char *filename, int *x, int *y, int *comp); +extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); +extern int stbi_jpeg_info_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp); + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define ZFAST_BITS 9 // accelerate all cases in default tables +#define ZFAST_MASK ((1 << ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + uint16 fast[1 << ZFAST_BITS]; + uint16 firstcode[16]; + int maxcode[17]; + uint16 firstsymbol[16]; + uint8 size[288]; + uint16 value[288]; +} zhuffman; + +__forceinline static int bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +__forceinline static int bit_reverse(int v, int bits) +{ + assert(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return bitreverse16(v) >> (16-bits); +} + +static int zbuild_huffman(zhuffman *z, uint8 *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 255, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + assert(sizes[i] <= (1 << i)); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (uint16) code; + z->firstsymbol[i] = (uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return e("bad codelengths","Corrupt JPEG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + z->size[c] = (uint8)s; + z->value[c] = (uint16)i; + if (s <= ZFAST_BITS) { + int k = bit_reverse(next_code[s],s); + while (k < (1 << ZFAST_BITS)) { + z->fast[k] = (uint16) c; + k += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +static uint8 *zbuffer, *zbuffer_end; + +__forceinline static int zget8(void) +{ + if (zbuffer >= zbuffer_end) return 0; + return *zbuffer++; +} + +//static unsigned long code_buffer; +static int num_bits; + +static void fill_bits(void) +{ + do { + assert(code_buffer < (1U << num_bits)); + code_buffer |= zget8() << num_bits; + num_bits += 8; + } while (num_bits <= 24); +} + +__forceinline static unsigned int zreceive(int n) +{ + unsigned int k; + if (num_bits < n) fill_bits(); + k = code_buffer & ((1 << n) - 1); + code_buffer >>= n; + num_bits -= n; + return k; +} + +__forceinline static int zhuffman_decode(zhuffman *z) +{ + int b,s,k; + if (num_bits < 16) fill_bits(); + b = z->fast[code_buffer & ZFAST_MASK]; + if (b < 0xffff) { + s = z->size[b]; + code_buffer >>= s; + num_bits -= s; + return z->value[b]; + } + + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = bit_reverse(code_buffer, 16); + for (s=ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + assert(z->size[b] == s); + code_buffer >>= s; + num_bits -= s; + return z->value[b]; +} + +static char *zout; +static char *zout_start; +static char *zout_end; +static int z_expandable; + +static int expand(int n) // need to make room for n bytes +{ + char *q; + int cur, limit; + if (!z_expandable) return e("output buffer limit","Corrupt PNG"); + cur = (int) (zout - zout_start); + limit = (int) (zout_end - zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) realloc(zout_start, limit); + if (q == NULL) return e("outofmem", "Out of memory"); + zout_start = q; + zout = q + cur; + zout_end = q + limit; + return 1; +} + +static zhuffman z_length, z_distance; + +static int length_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int length_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int dist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int parse_huffman_block(void) +{ + for(;;) { + int z = zhuffman_decode(&z_length); + if (z < 256) { + if (z < 0) return e("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= zout_end) if (!expand(1)) return 0; + *zout++ = (char) z; + } else { + uint8 *p; + int len,dist; + if (z == 256) return 1; + z -= 257; + len = length_base[z]; + if (length_extra[z]) len += zreceive(length_extra[z]); + z = zhuffman_decode(&z_distance); + if (z < 0) return e("bad huffman code","Corrupt PNG"); + dist = dist_base[z]; + if (dist_extra[z]) dist += zreceive(dist_extra[z]); + if (zout - zout_start < dist) return e("bad dist","Corrupt PNG"); + if (zout + len > zout_end) if (!expand(len)) return 0; + p = (uint8 *) (zout - dist); + while (len--) + *zout++ = *p++; + } + } +} + +static int compute_huffman_codes(void) +{ + static uint8 length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static zhuffman z_codelength; // static just to save stack space + uint8 lencodes[286+32+137];//padding for maximum single op + uint8 codelength_sizes[19]; + int i,n; + + int hlit = zreceive(5) + 257; + int hdist = zreceive(5) + 1; + int hclen = zreceive(4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = zreceive(3); + codelength_sizes[length_dezigzag[i]] = (uint8) s; + } + if (!zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = zhuffman_decode(&z_codelength); + assert(c >= 0 && c < 19); + if (c < 16) + lencodes[n++] = (uint8) c; + else if (c == 16) { + c = zreceive(2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = zreceive(3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + assert(c == 18); + c = zreceive(7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return e("bad codelengths","Corrupt PNG"); + if (!zbuild_huffman(&z_length, lencodes, hlit)) return 0; + if (!zbuild_huffman(&z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int parse_uncompressed_block(void) +{ + uint8 header[4]; + int len,nlen,k; + if (num_bits & 7) + zreceive(num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (num_bits > 0) { + header[k++] = (uint8) (code_buffer & 255); // wtf this warns? + code_buffer >>= 8; + num_bits -= 8; + } + assert(num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = (uint8) zget8(); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return e("zlib corrupt","Corrupt PNG"); + if (zbuffer + len > zbuffer_end) return e("read past buffer","Corrupt PNG"); + if (zout + len > zout_end) + if (!expand(len)) return 0; + memcpy(zout, zbuffer, len); + zbuffer += len; + zout += len; + return 1; +} + +static int parse_zlib_header(void) +{ + int cmf = zget8(); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = zget8(); + if ((cmf*256+flg) % 31 != 0) return e("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return e("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return e("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static uint8 default_length[288], default_distance[32]; +static void init_defaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) default_length[i] = 8; + for ( ; i <= 255; ++i) default_length[i] = 9; + for ( ; i <= 279; ++i) default_length[i] = 7; + for ( ; i <= 287; ++i) default_length[i] = 8; + + for (i=0; i <= 31; ++i) default_distance[i] = 5; +} + +static int parse_zlib(int parse_header) +{ + int final, type; + if (parse_header) + if (!parse_zlib_header()) return 0; + num_bits = 0; + code_buffer = 0; + do { + final = zreceive(1); + type = zreceive(2); + if (type == 0) { + if (!parse_uncompressed_block()) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!default_length[0]) init_defaults(); + if (!zbuild_huffman(&z_length , default_length , 288)) return 0; + if (!zbuild_huffman(&z_distance, default_distance, 32)) return 0; + } else { + if (!compute_huffman_codes()) return 0; + } + if (!parse_huffman_block()) return 0; + } + } while (!final); + return 1; +} + +static int do_zlib(char *obuf, int olen, int exp, int parse_header) +{ + zout_start = obuf; + zout = obuf; + zout_end = obuf + olen; + z_expandable = exp; + + return parse_zlib(parse_header); +} + +char *stbi_zlib_decode_malloc_guesssize(int initial_size, int *outlen) +{ + char *p = (char *) malloc(initial_size); + if (p == NULL) return NULL; + if (do_zlib(p, initial_size, 1, 1)) { + *outlen = (int) (zout - zout_start); + return zout_start; + } else { + free(zout_start); + return NULL; + } +} + +char *stbi_zlib_decode_malloc(char *buffer, int len, int *outlen) +{ + zbuffer = (uint8 *) buffer; + zbuffer_end = (uint8 *) buffer+len; + return stbi_zlib_decode_malloc_guesssize(16384, outlen); +} + +int stbi_zlib_decode_buffer(char *obuffer, int olen, char *ibuffer, int ilen) +{ + zbuffer = (uint8 *) ibuffer; + zbuffer_end = (uint8 *) ibuffer + ilen; + if (do_zlib(obuffer, olen, 0, 1)) + return (int) (zout - zout_start); + else + return -1; +} + +char *stbi_zlib_decode_noheader_malloc(char *buffer, int len, int *outlen) +{ + char *p = (char *) malloc(16384); + if (p == NULL) return NULL; + zbuffer = (uint8 *) buffer; + zbuffer_end = (uint8 *) buffer+len; + if (do_zlib(p, 16384, 1, 0)) { + *outlen = (int) (zout - zout_start); + return zout_start; + } else { + free(zout_start); + return NULL; + } +} + +int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, char *ibuffer, int ilen) +{ + zbuffer = (uint8 *) ibuffer; + zbuffer_end = (uint8 *) ibuffer + ilen; + if (do_zlib(obuffer, olen, 0, 0)) + return (int) (zout - zout_start); + else + return -1; +} + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + + +typedef struct +{ + unsigned long length; + unsigned long type; +} chunk; + +#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static chunk get_chunk_header(void) +{ + chunk c; + c.length = get32(); + c.type = get32(); + return c; +} + +static int check_png_header(void) +{ + static uint8 png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (get8() != png_sig[i]) return e("bad png sig","Not a PNG"); + return 1; +} + +static uint8 *idata, *expanded, *out; + +enum { + F_none=0, F_sub=1, F_up=2, F_avg=3, F_paeth=4, + F_avg_first, F_paeth_first, +}; + +static uint8 first_row_filter[5] = +{ + F_none, F_sub, F_none, F_avg_first, F_paeth_first +}; + +static int paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +// create the png data from post-deflated data +static int create_png_image(uint8 *raw, uint32 raw_len, int out_n) +{ + uint32 i,j,stride = img_x*out_n; + int k; + assert(out_n == img_n || out_n == img_n+1); + out = (uint8 *) malloc(img_x * img_y * out_n); + if (!out) return e("outofmem", "Out of memory"); + if (raw_len != (img_n * img_x + 1) * img_y) return e("not enough pixels","Corrupt PNG"); + for (j=0; j < img_y; ++j) { + uint8 *cur = out + stride*j; + uint8 *prior = cur - stride; + int filter = *raw++; + if (filter > 4) return e("invalid filter","Corrupt PNG"); + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + // handle first pixel explicitly + for (k=0; k < img_n; ++k) { + switch(filter) { + case F_none : cur[k] = raw[k]; break; + case F_sub : cur[k] = raw[k]; break; + case F_up : cur[k] = raw[k] + prior[k]; break; + case F_avg : cur[k] = raw[k] + (prior[k]>>1); break; + case F_paeth : cur[k] = (uint8) (raw[k] + paeth(0,prior[k],0)); break; + case F_avg_first : cur[k] = raw[k]; break; + case F_paeth_first: cur[k] = raw[k]; break; + } + } + if (img_n != out_n) cur[img_n] = 255; + raw += img_n; + cur += out_n; + prior += out_n; + // this is a little gross, so that we don't switch per-pixel or per-component + if (img_n == out_n) { + #define CASE(f) \ + case f: \ + for (i=1; i < img_x; ++i, raw+=img_n,cur+=img_n,prior+=img_n) \ + for (k=0; k < img_n; ++k) + switch(filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-img_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-img_n])>>1); break; + CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-img_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],0,0)); break; + } + #undef CASE + } else { + assert(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=1; i < img_x; ++i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) + switch(filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-out_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-out_n])>>1); break; + CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-out_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],0,0)); break; + } + #undef CASE + } + } + return 1; +} + +static int compute_transparency(uint8 tc[3], int out_n) +{ + uint32 i, pixel_count = img_x * img_y; + uint8 *p = out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + assert(out_n == 2 || out_n == 4); + + p = out; + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int expand_palette(uint8 *palette, int len, int pal_img_n) +{ + uint32 i, pixel_count = img_x * img_y; + uint8 *p, *temp_out, *orig = out; + + p = (uint8 *) malloc(pixel_count * pal_img_n); + if (p == NULL) return e("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + free(out); + out = temp_out; + return 1; +} + +static int parse_png_file(int scan, int req_comp) +{ + uint8 palette[1024], pal_img_n=0; + uint8 has_trans=0, tc[3]; + uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k; + + if (!check_png_header()) return 0; + + if (scan == SCAN_type) return 1; + + for(;;first=0) { + chunk c = get_chunk_header(); + if (first && c.type != PNG_TYPE('I','H','D','R')) + return e("first not IHDR","Corrupt PNG"); + switch (c.type) { + case PNG_TYPE('I','H','D','R'): { + int depth,color,interlace,comp,filter; + if (!first) return e("multiple IHDR","Corrupt PNG"); + if (c.length != 13) return e("bad IHDR len","Corrupt PNG"); + img_x = get32(); if (img_x > (1 << 24)) return e("too large","Very large image (corrupt?)"); + img_y = get32(); if (img_y > (1 << 24)) return e("too large","Very large image (corrupt?)"); + depth = get8(); if (depth != 8) return e("8bit only","PNG not supported: 8-bit only"); + color = get8(); if (color > 6) return e("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return e("bad ctype","Corrupt PNG"); + comp = get8(); if (comp) return e("bad comp method","Corrupt PNG"); + filter= get8(); if (filter) return e("bad filter method","Corrupt PNG"); + interlace = get8(); if (interlace) return e("interlaced","PNG not supported: interlaced mode"); + if (!img_x || !img_y) return e("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / img_x / img_n < img_y) return e("too large", "Image too large to decode"); + if (scan == SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + img_n = 1; + if ((1 << 30) / img_x / 4 < img_y) return e("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case PNG_TYPE('P','L','T','E'): { + if (c.length > 256*3) return e("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return e("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = get8u(); + palette[i*4+1] = get8u(); + palette[i*4+2] = get8u(); + palette[i*4+3] = 255; + } + break; + } + + case PNG_TYPE('t','R','N','S'): { + if (idata) return e("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == SCAN_header) { img_n = 4; return 1; } + if (pal_len == 0) return e("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return e("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = get8u(); + } else { + if (!(img_n & 1)) return e("tRNS with alpha","Corrupt PNG"); + if (c.length != (uint32) img_n*2) return e("bad tRNS len","Corrupt PNG"); + has_trans = 1; + for (k=0; k < img_n; ++k) + tc[k] = (uint8) get16(); // non 8-bit images will be larger + } + break; + } + + case PNG_TYPE('I','D','A','T'): { + if (pal_img_n && !pal_len) return e("no PLTE","Corrupt PNG"); + if (scan == SCAN_header) { img_n = pal_img_n; return 1; } + if (ioff + c.length > idata_limit) { + uint8 *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + p = (uint8 *) realloc(idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory"); + idata = p; + } + #ifndef STBI_NO_STDIO + if (img_file) + { + if (fread(idata+ioff,1,c.length,img_file) != c.length) return e("outofdata","Corrupt PNG"); + } + else + #endif + { + memcpy(idata+ioff, img_buffer, c.length); + img_buffer += c.length; + } + ioff += c.length; + break; + } + + case PNG_TYPE('I','E','N','D'): { + uint32 raw_len; + if (scan != SCAN_load) return 1; + if (idata == NULL) return e("no IDAT","Corrupt PNG"); + expanded = (uint8 *) stbi_zlib_decode_malloc((char *) idata, ioff, (int *) &raw_len); + if (expanded == NULL) return 0; // zlib should set error + free(idata); idata = NULL; + if ((req_comp == img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + img_out_n = img_n+1; + else + img_out_n = img_n; + if (!create_png_image(expanded, raw_len, img_out_n)) return 0; + if (has_trans) + if (!compute_transparency(tc, img_out_n)) return 0; + if (pal_img_n) { + // pal_img_n == 3 or 4 + img_n = pal_img_n; // record the actual colors we had + img_out_n = pal_img_n; + if (req_comp >= 3) img_out_n = req_comp; + if (!expand_palette(palette, pal_len, img_out_n)) + return 0; + } + free(expanded); expanded = NULL; + return 1; + } + + default: + // if critical, fail + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + static char invalid_chunk[] = "XXXX chunk not known"; + invalid_chunk[0] = (uint8) (c.type >> 24); + invalid_chunk[1] = (uint8) (c.type >> 16); + invalid_chunk[2] = (uint8) (c.type >> 8); + invalid_chunk[3] = (uint8) (c.type >> 0); + #endif + return e(invalid_chunk, "PNG not supported: unknown chunk type"); + } + skip(c.length); + break; + } + // end of chunk, read and skip CRC + get8(); get8(); get8(); get8(); + } +} + +static unsigned char *do_png(int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return ep("bad req_comp", "Internal error"); + if (parse_png_file(SCAN_load, req_comp)) { + result = out; + out = NULL; + if (req_comp && req_comp != img_out_n) { + result = convert_format(result, img_out_n, req_comp); + if (result == NULL) return result; + } + *x = img_x; + *y = img_y; + if (n) *n = img_n; + } + free(out); out = NULL; + free(expanded); expanded = NULL; + free(idata); idata = NULL; + + return result; +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_png_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + start_file(f); + return do_png(x,y,comp,req_comp); +} + +unsigned char *stbi_png_load(char *filename, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_png_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +unsigned char *stbi_png_load_from_memory(unsigned char *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + start_mem(buffer,len); + return do_png(x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +int stbi_png_test_file(FILE *f) +{ + int n,r; + n = ftell(f); + start_file(f); + r = parse_png_file(SCAN_type,STBI_default); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_png_test_memory(unsigned char *buffer, int len) +{ + start_mem(buffer, len); + return parse_png_file(SCAN_type,STBI_default); +} + +// TODO: load header from png +extern int stbi_png_info (char *filename, int *x, int *y, int *comp); +extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp); +extern int stbi_png_info_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp); + +// Microsoft/Windows BMP image + +static int bmp_test(void) +{ + int sz; + if (get8() != 'B') return 0; + if (get8() != 'M') return 0; + get32le(); // discard filesize + get16le(); // discard reserved + get16le(); // discard reserved + get32le(); // discard data offset + sz = get32le(); + if (sz == 12 || sz == 40 || sz == 56 || sz == 108) return 1; + return 0; +} + +#ifndef STBI_NO_STDIO +int stbi_bmp_test_file (FILE *f) +{ + int r,n = ftell(f); + start_file(f); + r = bmp_test(); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_bmp_test_memory (stbi_uc *buffer, int len) +{ + start_mem(buffer, len); + return bmp_test(); +} + +// returns 0..31 for the highest set bit +static int high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +static stbi_uc *bmp_load(int *x, int *y, int *comp, int req_comp) +{ + unsigned int mr=0,mg=0,mb=0,ma=0; + stbi_uc pal[256][4]; + int psize=0,i,j,compress=0,width; + int bpp, flip_vertically, pad, target, offset, hsz; + if (get8() != 'B' || get8() != 'M') return ep("not BMP", "Corrupt BMP"); + get32le(); // discard filesize + get16le(); // discard reserved + get16le(); // discard reserved + offset = get32le(); + hsz = get32le(); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) return ep("unknown BMP", "BMP type not supported: unknown"); + failure_reason = "bad BMP"; + if (hsz == 12) { + img_x = get16le(); + img_y = get16le(); + } else { + img_x = get32le(); + img_y = get32le(); + } + if (get16le() != 1) return 0; + bpp = get16le(); + if (bpp == 1) return ep("monochrome", "BMP type not supported: 1-bit"); + flip_vertically = img_y > 0; + img_y = abs(img_y); + if (hsz == 12) { + if (bpp < 24) + psize = (offset - 14 - 24) / 3; + } else { + compress = get32le(); + if (compress == 1 || compress == 2) return ep("BMP RLE", "BMP type not supported: RLE"); + get32le(); // discard sizeof + get32le(); // discard hres + get32le(); // discard vres + get32le(); // discard colorsused + get32le(); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + get32le(); + get32le(); + get32le(); + get32le(); + } + if (bpp == 16 || bpp == 32) { + mr = mg = mb = 0; + if (compress == 0) { + if (bpp == 32) { + mr = 0xff << 16; + mg = 0xff << 8; + mb = 0xff << 0; + } else { + mr = 31 << 10; + mg = 31 << 5; + mb = 31 << 0; + } + } else if (compress == 3) { + mr = get32le(); + mg = get32le(); + mb = get32le(); + // not documented, but generated by photoshop and handled by mspaint + if (mr == mg && mg == mb) { + // ?!?!? + return NULL; + } + } else + return NULL; + } + } else { + assert(hsz == 108); + mr = get32le(); + mg = get32le(); + mb = get32le(); + ma = get32le(); + get32le(); // discard color space + for (i=0; i < 12; ++i) + get32le(); // discard color space parameters + } + if (bpp < 16) + psize = (offset - 14 - hsz) >> 2; + } + img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = img_n; // if they want monochrome, we'll post-convert + out = (stbi_uc *) malloc(target * img_x * img_y); + if (!out) return ep("outofmem", "Out of memory"); + if (bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) return ep("invalid", "Corrupt BMP"); + for (i=0; i < psize; ++i) { + pal[i][2] = get8(); + pal[i][1] = get8(); + pal[i][0] = get8(); + if (hsz != 12) get8(); + pal[i][3] = 255; + } + skip(offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); + if (bpp == 4) width = (img_x + 1) >> 1; + else if (bpp == 8) width = img_x; + else return ep("bad bpp", "Corrupt BMP"); + pad = (-width)&3; + for (j=0; j < (int) img_y; ++j) { + for (i=0; i < (int) img_x; i += 2) { + int v=get8(),v2=0; + if (bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) img_x) break; + v = (bpp == 8) ? get8() : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + skip(pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + skip(offset - 14 - hsz); + if (bpp == 24) width = 3 * img_x; + else if (bpp == 16) width = 2*img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (bpp == 24) { + easy = 1; + } else if (bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0xff000000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) return ep("bad masks", "Corrupt BMP"); + // right shift amt to put high bit in position #7 + rshift = high_bit(mr)-7; rcount = bitcount(mr); + gshift = high_bit(mg)-7; gcount = bitcount(mr); + bshift = high_bit(mb)-7; bcount = bitcount(mr); + ashift = high_bit(ma)-7; acount = bitcount(mr); + } + for (j=0; j < (int) img_y; ++j) { + if (easy) { + for (i=0; i < (int) img_x; ++i) { + int a; + out[z+2] = get8(); + out[z+1] = get8(); + out[z+0] = get8(); + z += 3; + a = (easy == 2 ? get8() : 255); + if (target == 4) out[z++] = a; + } + } else { + for (i=0; i < (int) img_x; ++i) { + unsigned long v = (bpp == 16 ? get16le() : get32le()); + int a; + out[z++] = shiftsigned(v & mr, rshift, rcount); + out[z++] = shiftsigned(v & mg, gshift, gcount); + out[z++] = shiftsigned(v & mb, bshift, bcount); + a = (ma ? shiftsigned(v & ma, ashift, acount) : 255); + if (target == 4) out[z++] = a; + } + } + skip(pad); + } + } + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) img_y>>1; ++j) { + stbi_uc *p1 = out + j *img_x*target; + stbi_uc *p2 = out + (img_y-1-j)*img_x*target; + for (i=0; i < (int) img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = convert_format(out, target, req_comp); + if (out == NULL) return out; // convert_format frees input on failure + } + + *x = img_x; + *y = img_y; + if (comp) *comp = target; + return out; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_bmp_load (char *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = bmp_load(x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + start_file(f); + return bmp_load(x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_bmp_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + start_mem(buffer, len); + return bmp_load(x,y,comp,req_comp); +} + +// Targa Truevision - TGA +// by Jonathan Dummer + +static int tga_test(void) +{ + int sz; + get8u(); // discard Offset + sz = get8u(); // color type + if( sz > 1 ) return 0; // only RGB or indexed allowed + sz = get8u(); // image type + if( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE + get16(); // discard palette start + get16(); // discard palette length + get8(); // discard bits per palette color entry + get16(); // discard x origin + get16(); // discard y origin + if( get16() < 1 ) return 0; // test width + if( get16() < 1 ) return 0; // test height + sz = get8(); // bits per pixel + if( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) return 0; // only RGB or RGBA or grey allowed + return 1; // seems to have passed everything +} + +#ifndef STBI_NO_STDIO +int stbi_tga_test_file (FILE *f) +{ + int r,n = ftell(f); + start_file(f); + r = tga_test(); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_tga_test_memory (stbi_uc *buffer, int len) +{ + start_mem(buffer, len); + return tga_test(); +} + +static stbi_uc *tga_load(int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = get8u(); + int tga_indexed = get8u(); + int tga_image_type = get8u(); + int tga_is_RLE = 0; + int tga_palette_start = get16le(); + int tga_palette_len = get16le(); + int tga_palette_bits = get8u(); + int tga_x_origin = get16le(); + int tga_y_origin = get16le(); + int tga_width = get16le(); + int tga_height = get16le(); + int tga_bits_per_pixel = get8u(); + int tga_inverted = get8u(); + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[] = { 0, 0, 0, 0 }; + unsigned char trans_data[] = { 0, 0, 0, 0 }; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + // do a tiny bit of precessing + if( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + /* int tga_alpha_bits = tga_inverted & 15; */ + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // error check + if( //(tga_indexed) || + (tga_width < 1) || (tga_height < 1) || + (tga_image_type < 1) || (tga_image_type > 3) || + ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && + (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) + ) + { + return NULL; + } + + // If I'm paletted, then I'll use the number of bits from the palette + if( tga_indexed ) + { + tga_bits_per_pixel = tga_palette_bits; + } + + // tga info + *x = tga_width; + *y = tga_height; + if( (req_comp < 1) || (req_comp > 4) ) + { + // just use whatever the file was + req_comp = tga_bits_per_pixel / 8; + *comp = req_comp; + } else + { + // force a new number of components + *comp = req_comp; + } + tga_data = (unsigned char*)malloc( tga_width * tga_height * req_comp ); + + // skip to the data's starting position (offset usually = 0) + skip( tga_offset ); + // do I need to load a palette? + if( tga_indexed ) + { + // any data to skip? (offset usually = 0) + skip( tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)malloc( tga_palette_len * tga_palette_bits / 8 ); + getn( tga_palette, tga_palette_len * tga_palette_bits / 8 ); + } + // load the data + for( i = 0; i < tga_width * tga_height; ++i ) + { + // if I'm in RLE mode, do I need to get a RLE chunk? + if( tga_is_RLE ) + { + if( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = get8u(); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if( read_next_pixel ) + { + // load however much data we did have + if( tga_indexed ) + { + // read in 1 byte, then perform the lookup + int pal_idx = get8u(); + if( pal_idx >= tga_palette_len ) + { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_bits_per_pixel / 8; + for( j = 0; j*8 < tga_bits_per_pixel; ++j ) + { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else + { + // read in the data raw + for( j = 0; j*8 < tga_bits_per_pixel; ++j ) + { + raw_data[j] = get8u(); + } + } + // convert raw to the intermediate format + switch( tga_bits_per_pixel ) + { + case 8: + // Luminous => RGBA + trans_data[0] = raw_data[0]; + trans_data[1] = raw_data[0]; + trans_data[2] = raw_data[0]; + trans_data[3] = 255; + break; + case 16: + // Luminous,Alpha => RGBA + trans_data[0] = raw_data[0]; + trans_data[1] = raw_data[0]; + trans_data[2] = raw_data[0]; + trans_data[3] = raw_data[1]; + break; + case 24: + // BGR => RGBA + trans_data[0] = raw_data[2]; + trans_data[1] = raw_data[1]; + trans_data[2] = raw_data[0]; + trans_data[3] = 255; + break; + case 32: + // BGRA => RGBA + trans_data[0] = raw_data[2]; + trans_data[1] = raw_data[1]; + trans_data[2] = raw_data[0]; + trans_data[3] = raw_data[3]; + break; + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + // convert to final format + switch( req_comp ) + { + case 1: + // RGBA => Luminous + tga_data[i*req_comp+0] = (trans_data[0] + trans_data[1] + trans_data[2]) / 3; + break; + case 2: + // RGBA => Luminous,Alpha + tga_data[i*req_comp+0] = (trans_data[0] + trans_data[1] + trans_data[2]) / 3; + tga_data[i*req_comp+1] = trans_data[3]; + break; + case 3: + // RGBA => RGB + tga_data[i*req_comp+0] = trans_data[0]; + tga_data[i*req_comp+1] = trans_data[1]; + tga_data[i*req_comp+2] = trans_data[2]; + break; + case 4: + // RGBA => RGBA + tga_data[i*req_comp+0] = trans_data[0]; + tga_data[i*req_comp+1] = trans_data[1]; + tga_data[i*req_comp+2] = trans_data[2]; + tga_data[i*req_comp+3] = trans_data[3]; + break; + } + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if( tga_inverted ) + { + for( j = 0; j*2 < tga_height; ++j ) + { + int index1 = j * tga_width * req_comp; + int index2 = (tga_height - 1 - j) * tga_width * req_comp; + for( i = tga_width * req_comp; i > 0; --i ) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if( tga_palette != NULL ) + { + free( tga_palette ); + } + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_tga_load (char *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = tga_load(x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + start_file(f); + return tga_load(x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_tga_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + start_mem(buffer, len); + return tga_load(x,y,comp,req_comp); +} + +/// DDS file support, does decoding, _not_ direct uploading +/// (use SOIL for that ;-) + +/// A bunch of DirectDraw Surface structures and flags +typedef struct { + unsigned int dwMagic; + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; + unsigned int dwMipMapCount; + unsigned int dwReserved1[ 11 ]; + + // DDPIXELFORMAT + struct { + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + unsigned int dwRGBBitCount; + unsigned int dwRBitMask; + unsigned int dwGBitMask; + unsigned int dwBBitMask; + unsigned int dwAlphaBitMask; + } sPixelFormat; + + // DDCAPS2 + struct { + unsigned int dwCaps1; + unsigned int dwCaps2; + unsigned int dwDDSX; + unsigned int dwReserved; + } sCaps; + unsigned int dwReserved2; +} DDS_header ; + +// the following constants were copied directly off the MSDN website + +// The dwFlags member of the original DDSURFACEDESC2 structure +// can be set to one or more of the following values. +#define DDSD_CAPS 0x00000001 +#define DDSD_HEIGHT 0x00000002 +#define DDSD_WIDTH 0x00000004 +#define DDSD_PITCH 0x00000008 +#define DDSD_PIXELFORMAT 0x00001000 +#define DDSD_MIPMAPCOUNT 0x00020000 +#define DDSD_LINEARSIZE 0x00080000 +#define DDSD_DEPTH 0x00800000 + +// DirectDraw Pixel Format +#define DDPF_ALPHAPIXELS 0x00000001 +#define DDPF_FOURCC 0x00000004 +#define DDPF_RGB 0x00000040 + +// The dwCaps1 member of the DDSCAPS2 structure can be +// set to one or more of the following values. +#define DDSCAPS_COMPLEX 0x00000008 +#define DDSCAPS_TEXTURE 0x00001000 +#define DDSCAPS_MIPMAP 0x00400000 + +// The dwCaps2 member of the DDSCAPS2 structure can be +// set to one or more of the following values. +#define DDSCAPS2_CUBEMAP 0x00000200 +#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 +#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 +#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 +#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 +#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 +#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 +#define DDSCAPS2_VOLUME 0x00200000 + +static int dds_test(void) +{ + // check the magic number + if (get8() != 'D') return 0; + if (get8() != 'D') return 0; + if (get8() != 'S') return 0; + if (get8() != ' ') return 0; + // check header size + if (get32le() != 124) return 0; + return 1; +} +#ifndef STBI_NO_STDIO +int stbi_dds_test_file (FILE *f) +{ + int r,n = ftell(f); + start_file(f); + r = dds_test(); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_dds_test_memory (stbi_uc *buffer, int len) +{ + start_mem(buffer, len); + return dds_test(); +} + +// helper functions +int stbi_convert_bit_range( int c, int from_bits, int to_bits ) +{ + int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); + return (b + (b >> from_bits)) >> from_bits; +} +void stbi_rgb_888_from_565( unsigned int c, int *r, int *g, int *b ) +{ + *r = stbi_convert_bit_range( (c >> 11) & 31, 5, 8 ); + *g = stbi_convert_bit_range( (c >> 05) & 63, 6, 8 ); + *b = stbi_convert_bit_range( (c >> 00) & 31, 5, 8 ); +} +void stbi_decode_DXT1_block( + unsigned char uncompressed[16*4], + unsigned char compressed[8] ) +{ + int next_bit = 4*8; + int i, r, g, b; + int c0, c1; + unsigned char decode_colors[4*4]; + // find the 2 primary colors + c0 = compressed[0] + (compressed[1] << 8); + c1 = compressed[2] + (compressed[3] << 8); + stbi_rgb_888_from_565( c0, &r, &g, &b ); + decode_colors[0] = r; + decode_colors[1] = g; + decode_colors[2] = b; + decode_colors[3] = 255; + stbi_rgb_888_from_565( c1, &r, &g, &b ); + decode_colors[4] = r; + decode_colors[5] = g; + decode_colors[6] = b; + decode_colors[7] = 255; + if( c0 > c1 ) + { + // no alpha, 2 interpolated colors + decode_colors[8] = (2*decode_colors[0] + decode_colors[4]) / 3; + decode_colors[9] = (2*decode_colors[1] + decode_colors[5]) / 3; + decode_colors[10] = (2*decode_colors[2] + decode_colors[6]) / 3; + decode_colors[11] = 255; + decode_colors[12] = (decode_colors[0] + 2*decode_colors[4]) / 3; + decode_colors[13] = (decode_colors[1] + 2*decode_colors[5]) / 3; + decode_colors[14] = (decode_colors[2] + 2*decode_colors[6]) / 3; + decode_colors[15] = 255; + } else + { + // 1 interpolated color, alpha + decode_colors[8] = (decode_colors[0] + decode_colors[4]) / 2; + decode_colors[9] = (decode_colors[1] + decode_colors[5]) / 2; + decode_colors[10] = (decode_colors[2] + decode_colors[6]) / 2; + decode_colors[11] = 255; + decode_colors[12] = 0; + decode_colors[13] = 0; + decode_colors[14] = 0; + decode_colors[15] = 0; + } + // decode the block + for( i = 0; i < 16*4; i += 4 ) + { + int idx = ((compressed[next_bit>>3] >> (next_bit & 7)) & 3) * 4; + next_bit += 2; + uncompressed[i+0] = decode_colors[idx+0]; + uncompressed[i+1] = decode_colors[idx+1]; + uncompressed[i+2] = decode_colors[idx+2]; + uncompressed[i+3] = decode_colors[idx+3]; + } + // done +} +void stbi_decode_DXT23_alpha_block( + unsigned char uncompressed[16*4], + unsigned char compressed[8] ) +{ + int i, next_bit = 0; + // each alpha value gets 4 bits + for( i = 3; i < 16*4; i += 4 ) + { + uncompressed[i] = stbi_convert_bit_range( + (compressed[next_bit>>3] >> (next_bit&7)) & 15, + 4, 8 ); + next_bit += 4; + } +} +void stbi_decode_DXT45_alpha_block( + unsigned char uncompressed[16*4], + unsigned char compressed[8] ) +{ + int i, next_bit = 8*2; + unsigned char decode_alpha[8]; + // each alpha value gets 3 bits, and the 1st 2 bytes are the range + decode_alpha[0] = compressed[0]; + decode_alpha[1] = compressed[1]; + if( decode_alpha[0] > decode_alpha[1] ) + { + // 6 step intermediate + decode_alpha[2] = (6*decode_alpha[0] + 1*decode_alpha[1]) / 7; + decode_alpha[3] = (5*decode_alpha[0] + 2*decode_alpha[1]) / 7; + decode_alpha[4] = (4*decode_alpha[0] + 3*decode_alpha[1]) / 7; + decode_alpha[5] = (3*decode_alpha[0] + 4*decode_alpha[1]) / 7; + decode_alpha[6] = (2*decode_alpha[0] + 5*decode_alpha[1]) / 7; + decode_alpha[7] = (1*decode_alpha[0] + 6*decode_alpha[1]) / 7; + } else + { + // 4 step intermediate, pluss full and none + decode_alpha[2] = (4*decode_alpha[0] + 1*decode_alpha[1]) / 5; + decode_alpha[3] = (3*decode_alpha[0] + 2*decode_alpha[1]) / 5; + decode_alpha[4] = (2*decode_alpha[0] + 3*decode_alpha[1]) / 5; + decode_alpha[5] = (1*decode_alpha[0] + 4*decode_alpha[1]) / 5; + decode_alpha[6] = 0; + decode_alpha[7] = 255; + } + for( i = 3; i < 16*4; i += 4 ) + { + int idx = 0, bit; + bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; + idx += bit << 0; + ++next_bit; + bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; + idx += bit << 1; + ++next_bit; + bit = (compressed[next_bit>>3] >> (next_bit&7)) & 1; + idx += bit << 2; + ++next_bit; + uncompressed[i] = decode_alpha[idx & 7]; + } + // done +} +void stbi_decode_DXT_color_block( + unsigned char uncompressed[16*4], + unsigned char compressed[8] ) +{ + int next_bit = 4*8; + int i, r, g, b; + int c0, c1; + unsigned char decode_colors[4*3]; + // find the 2 primary colors + c0 = compressed[0] + (compressed[1] << 8); + c1 = compressed[2] + (compressed[3] << 8); + stbi_rgb_888_from_565( c0, &r, &g, &b ); + decode_colors[0] = r; + decode_colors[1] = g; + decode_colors[2] = b; + stbi_rgb_888_from_565( c1, &r, &g, &b ); + decode_colors[3] = r; + decode_colors[4] = g; + decode_colors[5] = b; + // Like DXT1, but no choicees: + // no alpha, 2 interpolated colors + decode_colors[6] = (2*decode_colors[0] + decode_colors[3]) / 3; + decode_colors[7] = (2*decode_colors[1] + decode_colors[4]) / 3; + decode_colors[8] = (2*decode_colors[2] + decode_colors[5]) / 3; + decode_colors[9] = (decode_colors[0] + 2*decode_colors[3]) / 3; + decode_colors[10] = (decode_colors[1] + 2*decode_colors[4]) / 3; + decode_colors[11] = (decode_colors[2] + 2*decode_colors[5]) / 3; + // decode the block + for( i = 0; i < 16*4; i += 4 ) + { + int idx = ((compressed[next_bit>>3] >> (next_bit & 7)) & 3) * 3; + next_bit += 2; + uncompressed[i+0] = decode_colors[idx+0]; + uncompressed[i+1] = decode_colors[idx+1]; + uncompressed[i+2] = decode_colors[idx+2]; + } + // done +} +static stbi_uc *dds_load(int *x, int *y, int *comp, int req_comp) +{ + // all variables go up front + stbi_uc *dds_data = NULL; + stbi_uc block[16*4]; + stbi_uc compressed[8]; + int flags, DXT_family; + int has_alpha, has_mipmap; + int is_compressed, cubemap_faces; + int block_pitch, num_blocks; + DDS_header header; + int i, sz, cf; + // load the header + if( sizeof( DDS_header ) != 128 ) + { + return NULL; + } + getn( (stbi_uc*)(&header), 128 ); + // and do some checking + if( header.dwMagic != (('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24)) ) return NULL; + if( header.dwSize != 124 ) return NULL; + flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; + if( (header.dwFlags & flags) != flags ) return NULL; + /* 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 */ + if( header.sPixelFormat.dwSize != 32 ) return NULL; + flags = DDPF_FOURCC | DDPF_RGB; + if( (header.sPixelFormat.dwFlags & flags) == 0 ) return NULL; + if( (header.sCaps.dwCaps1 & DDSCAPS_TEXTURE) == 0 ) return NULL; + // get the image data + img_x = header.dwWidth; + img_y = header.dwHeight; + img_n = 4; + is_compressed = (header.sPixelFormat.dwFlags & DDPF_FOURCC) / DDPF_FOURCC; + has_alpha = (header.sPixelFormat.dwFlags & DDPF_ALPHAPIXELS) / DDPF_ALPHAPIXELS; + has_mipmap = (header.sCaps.dwCaps1 & DDSCAPS_MIPMAP) && (header.dwMipMapCount > 1); + cubemap_faces = (header.sCaps.dwCaps2 & DDSCAPS2_CUBEMAP) / DDSCAPS2_CUBEMAP; + /* I need cubemaps to have square faces */ + cubemap_faces &= (img_x == img_y); + cubemap_faces *= 5; + cubemap_faces += 1; + block_pitch = (img_x+3) >> 2; + num_blocks = block_pitch * ((img_y+3) >> 2); + /* let the user know what's going on */ + *x = img_x; + *y = img_y; + *comp = img_n; + /* is this uncompressed? */ + if( is_compressed ) + { + /* compressed */ + // note: header.sPixelFormat.dwFourCC is something like (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24)) + DXT_family = 1 + (header.sPixelFormat.dwFourCC >> 24) - '1'; + if( (DXT_family < 1) || (DXT_family > 5) ) return NULL; + /* check the expected size...oops, nevermind... + those non-compliant writers leave + dwPitchOrLinearSize == 0 */ + // passed all the tests, get the RAM for decoding + sz = (img_x)*(img_y)*4*cubemap_faces; + dds_data = (unsigned char*)malloc( sz ); + /* do this once for each face */ + for( cf = 0; cf < cubemap_faces; ++ cf ) + { + // now read and decode all the blocks + for( i = 0; i < num_blocks; ++i ) + { + // where are we? + int bx, by, bw=4, bh=4; + int ref_x = 4 * (i % block_pitch); + int ref_y = 4 * (i / block_pitch); + // get the next block's worth of compressed data, and decompress it + if( DXT_family == 1 ) + { + // DXT1 + getn( compressed, 8 ); + stbi_decode_DXT1_block( block, compressed ); + } else if( DXT_family < 4 ) + { + // DXT2/3 + getn( compressed, 8 ); + stbi_decode_DXT23_alpha_block ( block, compressed ); + getn( compressed, 8 ); + stbi_decode_DXT_color_block ( block, compressed ); + } else + { + // DXT4/5 + getn( compressed, 8 ); + stbi_decode_DXT45_alpha_block ( block, compressed ); + getn( compressed, 8 ); + stbi_decode_DXT_color_block ( block, compressed ); + } + // is this a partial block? + if( ref_x + 4 > img_x ) + { + bw = img_x - ref_x; + } + if( ref_y + 4 > img_y ) + { + bh = img_y - ref_y; + } + // now drop our decompressed data into the buffer + for( by = 0; by < bh; ++by ) + { + int idx = 4*((ref_y+by+cf*img_x)*img_x + ref_x); + for( bx = 0; bx < bw*4; ++bx ) + { + + dds_data[idx+bx] = block[by*16+bx]; + } + } + } + /* done reading and decoding the main image... + skip MIPmaps if present */ + if( has_mipmap ) + { + int block_size = 16; + if( DXT_family == 1 ) + { + block_size = 8; + } + for( i = 1; i < header.dwMipMapCount; ++i ) + { + int mx = img_x >> (i + 2); + int my = img_y >> (i + 2); + if( mx < 1 ) + { + mx = 1; + } + if( my < 1 ) + { + my = 1; + } + skip( mx*my*block_size ); + } + } + }/* per cubemap face */ + } else + { + /* uncompressed */ + DXT_family = 0; + img_n = 3; + if( has_alpha ) + { + img_n = 4; + } + *comp = img_n; + sz = img_x*img_y*img_n*cubemap_faces; + dds_data = (unsigned char*)malloc( sz ); + /* do this once for each face */ + for( cf = 0; cf < cubemap_faces; ++ cf ) + { + /* read the main image for this face */ + getn( &dds_data[cf*img_x*img_y*img_n], img_x*img_y*img_n ); + /* done reading and decoding the main image... + skip MIPmaps if present */ + if( has_mipmap ) + { + for( i = 1; i < header.dwMipMapCount; ++i ) + { + int mx = img_x >> i; + int my = img_y >> i; + if( mx < 1 ) + { + mx = 1; + } + if( my < 1 ) + { + my = 1; + } + skip( mx*my*img_n ); + } + } + } + /* data was BGR, I need it RGB */ + for( i = 0; i < sz; i += img_n ) + { + unsigned char temp = dds_data[i]; + dds_data[i] = dds_data[i+2]; + dds_data[i+2] = temp; + } + } + /* finished decompressing into RGBA, + adjust the y size if we have a cubemap + note: sz is already up to date */ + img_y *= cubemap_faces; + *y = img_y; + // did the user want something else, or + // see if all the alpha values are 255 (i.e. no transparency) + has_alpha = 0; + if( img_n == 4) + { + for( i = 3; (i < sz) && (has_alpha == 0); i += 4 ) + { + has_alpha |= (dds_data[i] < 255); + } + } + if( (req_comp <= 4) && (req_comp >= 1) && (req_comp != img_n) ) + { + // user has some requirements, meet them + dds_data = convert_format( dds_data, img_n, req_comp ); + *comp = req_comp; + } else + { + // user had no requirements, only drop to RGB is no alpha + if( (has_alpha == 0) && (img_n == 4) ) + { + dds_data = convert_format( dds_data, 4, 3 ); + *comp = 3; + } + } + // OK, done + return dds_data; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_dds_load (char *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = dds_load(x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + start_file(f); + return dds_load(x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_dds_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + start_mem(buffer, len); + return dds_load(x,y,comp,req_comp); +} + + +/////////////////////// write image /////////////////////// + +#ifndef STBI_NO_WRITE + +static void write8(FILE *f, int x) { uint8 z = (uint8) x; fwrite(&z,1,1,f); } + +static void writefv(FILE *f, char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { uint8 x = va_arg(v, int); write8(f,x); break; } + case '2': { int16 x = va_arg(v, int); write8(f,x); write8(f,x>>8); break; } + case '4': { int32 x = va_arg(v, int); write8(f,x); write8(f,x>>8); write8(f,x>>16); write8(f,x>>24); break; } + default: + assert(0); + va_end(v); + return; + } + } +} + +static void writef(FILE *f, char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + writefv(f,fmt,v); + va_end(v); +} + +static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad) +{ + uint8 bg[3] = { 255, 0, 255}, px[3]; + uint32 zero = 0; + int i,j,k, j_end; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + uint8 *d = (uint8 *) data + (j*x+i)*comp; + if (write_alpha < 0) + fwrite(&d[comp-1], 1, 1, f); + switch (comp) { + case 1: + case 2: writef(f, "111", d[0],d[0],d[0]); + break; + case 4: + if (!write_alpha) { + for (k=0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255; + writef(f, "111", px[1-rgb_dir],px[1],px[1+rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + writef(f, "111", d[1-rgb_dir],d[1],d[1+rgb_dir]); + break; + } + if (write_alpha > 0) + fwrite(&d[comp-1], 1, 1, f); + } + fwrite(&zero,scanline_pad,1,f); + } +} + +static int outfile(char *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, char *fmt, ...) +{ + FILE *f = fopen(filename, "wb"); + if (f) { + va_list v; + va_start(v, fmt); + writefv(f, fmt, v); + va_end(v); + write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); + fclose(f); + } + return f != NULL; +} + +int stbi_write_bmp(char *filename, int x, int y, int comp, void *data) +{ + int pad = (-x*3) & 3; + return outfile(filename,-1,-1,x,y,comp,data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +int stbi_write_tga(char *filename, int x, int y, int comp, void *data) +{ + int has_alpha = !(comp & 1); + return outfile(filename, -1,-1, x, y, comp, data, has_alpha, 0, + "111 221 2222 11", 0,0,2, 0,0,0, 0,0,x,y, 24+8*has_alpha, 8*has_alpha); +} + +// any other image formats that do interleaved rgb data? +// PNG: requires adler32,crc32 -- significant amount of code +// PSD: no, channels output separately +// TIFF: no, stripwise-interleaved... i think + +#endif // STBI_NO_WRITE diff --git a/src/projectM-engine/stb_image.h b/src/projectM-engine/stb_image.h new file mode 100644 index 000000000..79d53c00c --- /dev/null +++ b/src/projectM-engine/stb_image.h @@ -0,0 +1,253 @@ +/* stbi-1.00 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c + when you control the images you're loading + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline (no JPEG progressive, no oddball channel decimations) + PNG non-interlaced + BMP non-1bpp, non-RLE + writes BMP,TGA (define STBI_NO_WRITE to remove code) + decoded from memory or through stdio FILE (define STBI_NO_STDIO to remove code) + + JLD: + added DDS decoding code + added a few header guards, re-arrangements, etc. + added the TGA loading code (though that is in the cannonical version now [8^) + + TODO: + stbi_info_* + PSD loader + + history: + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less + than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant +*/ + + +/* +//// begin header file //////////////////////////////////////////////////// +// +// Limitations: +// - no progressive/interlaced support (jpeg, png) +// - 8-bit samples only (jpeg, png) +// - not threadsafe +// - channel subsampling of at most 2 in each dimension (jpeg) +// - no delayed line count (jpeg) -- IJG doesn't support either +// +// Basic usage: +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to easily see if it's opaque. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG and BMP images are automatically depalettized. +*/ + +#ifndef HEADER_STB_IMAGE +#define HEADER_STB_IMAGE + +#ifndef STBI_NO_STDIO +#include +#endif + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +// WRITING API + +#if !defined(STBI_NO_WRITE) && !defined(STBI_NO_STDIO) +// write a BMP/TGA file given tightly packed 'comp' channels (no padding, nor bmp-stride-padding) +// (you must include the appropriate extension in the filename). +// returns TRUE on success, FALSE if couldn't open file, error writing file +extern int stbi_write_bmp (char *filename, int x, int y, int comp, void *data); +extern int stbi_write_tga (char *filename, int x, int y, int comp, void *data); +#endif + +// PRIMARY API - works on images of any type + +// load image by filename, open file, or memory buffer +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_load (char *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif +extern stbi_uc *stbi_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image + +// get a VERY brief reason for failure +extern char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +extern void stbi_image_free (stbi_uc *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +extern int stbi_info_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp); +#ifndef STBI_NO_STDIO +extern int stbi_info (char *filename, int *x, int *y, int *comp); +#endif + +// ZLIB client - used by PNG, available for other purposes + +extern char *stbi_zlib_decode_malloc_guesssize(int initial_size, int *outlen); +extern char *stbi_zlib_decode_malloc(char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_buffer(char *obuffer, int olen, char *ibuffer, int ilen); + +extern char *stbi_zlib_decode_noheader_malloc(char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, char *ibuffer, int ilen); + + +// TYPE-SPECIFIC ACCESS + +// is it a jpeg? +extern int stbi_jpeg_test_memory (stbi_uc *buffer, int len); +extern stbi_uc *stbi_jpeg_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_jpeg_info_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_jpeg_load (char *filename, int *x, int *y, int *comp, int req_comp); +extern int stbi_jpeg_test_file (FILE *f); +extern stbi_uc *stbi_jpeg_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + +extern int stbi_jpeg_info (char *filename, int *x, int *y, int *comp); +extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +extern int stbi_jpeg_dc_only; // only decode DC component + +// is it a png? +extern int stbi_png_test_memory (stbi_uc *buffer, int len); +extern stbi_uc *stbi_png_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_png_load (char *filename, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info (char *filename, int *x, int *y, int *comp); +extern int stbi_png_test_file (FILE *f); +extern stbi_uc *stbi_png_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +// is it a bmp? +extern int stbi_bmp_test_memory (stbi_uc *buffer, int len); + +extern stbi_uc *stbi_bmp_load (char *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_bmp_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_bmp_test_file (FILE *f); +extern stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a tga? +extern int stbi_tga_test_memory (stbi_uc *buffer, int len); + +extern stbi_uc *stbi_tga_load (char *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_tga_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_tga_test_file (FILE *f); +extern stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a DDS file? +extern int stbi_dds_test_memory (stbi_uc *buffer, int len); + +extern stbi_uc *stbi_dds_load (char *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_dds_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_dds_test_file (FILE *f); +extern stbi_uc *stbi_dds_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// define new loaders +typedef struct +{ + int (*test_memory)(stbi_uc *buffer, int len); + stbi_uc * (*load_from_memory)(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp); + #ifndef STBI_NO_STDIO + int (*test_file)(FILE *f); + stbi_uc * (*load_from_file)(FILE *f, int *x, int *y, int *comp, int req_comp); + #endif +} stbi_loader; + +// register a loader by filling out the above structure (you must defined ALL functions) +// returns 1 if added or already added, 0 if not added (too many loaders) +extern int stbi_register_loader(stbi_loader *loader); + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // HEADER_STB_IMAGE diff --git a/src/projectM-engine/texture.cpp b/src/projectM-engine/texture.cpp deleted file mode 100644 index 99e2a84f5..000000000 --- a/src/projectM-engine/texture.cpp +++ /dev/null @@ -1,268 +0,0 @@ - -#include -#include -#include "bmpLoader.h" -#include "pcxLoader.h" -#include "tgaLoader.h" -#include -#include -#include - -#ifdef LINUX -#include -#endif - -#ifdef WIN32 -#include -#endif - -// openGL extensions if not present, define them -#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 - #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 - #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 - #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 -#endif - -#ifndef GL_TEXTURE_COMPRESSED_IMAGE_SIZE -#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 -#endif - -//-------------------------------------------------------------------------- -/// \brief This function takes raw image data and converts it to an openGL -/// texture. -/// \param Format - GL_RGB,GL_RGBA,GL_ALPHA -/// \param pixels - the pixel data -/// \param w - the image width -/// \param h - the image height -/// \param compressed - true for DXT compression -/// - -unsigned int MakeGlTexture(GLenum Format,const unsigned char *pixels,unsigned int w,unsigned int h,bool compressed) -{ - unsigned int texObject; - - glGenTextures(1,&texObject); - - glPixelStorei (GL_UNPACK_ALIGNMENT, 0); - glBindTexture (GL_TEXTURE_2D, texObject); - - glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - if(compressed) { - switch(Format) { - // - case GL_RGB: - glTexImage2D (GL_TEXTURE_2D,0,GL_COMPRESSED_RGB_S3TC_DXT1_EXT,w,h,0,GL_RGB,GL_UNSIGNED_BYTE,pixels); - break; - - // - case GL_RGBA: - glTexImage2D (GL_TEXTURE_2D,0,GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,pixels); - break; - - // - case GL_ALPHA: - glTexImage2D (GL_TEXTURE_2D,0,GL_ALPHA,w,h,0,GL_ALPHA,GL_UNSIGNED_BYTE,pixels); - break; - - default: ; - } - } - else { - glTexImage2D (GL_TEXTURE_2D,0,Format,w,h,0,Format,GL_UNSIGNED_BYTE,pixels); - } - - return texObject; - -} - -/// this structure is used to reference count OpenGL textures -struct TexRef { - - /// the openGL texture object - unsigned int idx; - - /// the reference count - unsigned int ref; - - /// the size of the loaded texture - unsigned int size; - - /// ctor - TexRef(unsigned id_,unsigned sz) : idx(id_),ref(1),size(sz) {} - /// ctor - TexRef() : idx(0),ref(0),size(0) {} - /// ctor - TexRef(const TexRef& tr) : idx(tr.idx),ref(tr.ref),size(tr.size) {} -}; - -/// a global map of all currently loaded textures -std::map g_Textures; - -//-------------------------------------------------------------------------- -/// \brief This function loads the specified image file and returns the -/// openGL texture object for it. Only tga, bmp and pcx images are -/// supported. -/// \param filename - the image file to use for the texture -/// \return compressed - if true DXT compression will be used. -/// -unsigned int LoadTexture(const char* filename,bool compressed) { - - // check to see if file is already loaded. - std::map::iterator it = g_Textures.find(filename); - if ( it!=g_Textures.end()) { - it->second.ref++; - return it->second.idx; - } - - // if not, try to load the file. - unsigned int w=0,h=0,bpp=0; - unsigned char* pixels=0; - - int len = static_cast(strlen(filename)); - - // load a bmp - if( (filename[len-3] == 'b' || filename[len-3] == 'B') && - (filename[len-2] == 'm' || filename[len-2] == 'M') && - (filename[len-1] == 'p' || filename[len-1] == 'P') ) { - if(!LoadBmpImage(filename,&pixels,&w,&h,&bpp)) - return 0; - } - else - // load a pcx - if( (filename[len-3] == 'p' || filename[len-3] == 'P') && - (filename[len-2] == 'c' || filename[len-2] == 'C') && - (filename[len-1] == 'x' || filename[len-1] == 'X') ) { - if(!LoadPcxImage(filename,&pixels,&w,&h,&bpp)) - return 0; - } - else - // load a tga - if( (filename[len-3] == 't' || filename[len-3] == 'T') && - (filename[len-2] == 'g' || filename[len-2] == 'G') && - (filename[len-1] == 'a' || filename[len-1] == 'A') ) { - if(!LoadTgaImage(filename,&pixels,&w,&h,&bpp)) - return 0; - } - else { - std::cerr << "Unsupported image format\n"; - return 0; - } - - // generat the correct texture type for the image - unsigned int tex_object; - switch(bpp) { - case 1: - tex_object = MakeGlTexture(GL_ALPHA,pixels,w,h,compressed); - break; - case 2: - break; - case 3: - tex_object = MakeGlTexture(GL_RGB,pixels,w,h,compressed); - break; - case 4: - tex_object = MakeGlTexture(GL_RGBA,pixels,w,h,compressed); - break; - default: - break; - } - - int data_size=0; - if(compressed) { - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE,&data_size); - } - else { - data_size = bpp*w*h; - } - - - // delete the pixel data because we no longer need it - free(pixels); - - // insert the texture into a map to keep track of it - g_Textures.insert( std::make_pair( std::string(filename), TexRef(tex_object,data_size) ) ); - - // return GL texture object - return tex_object; - -} - -//-------------------------------------------------------------------------- -/// \brief This function will release the specified texture (as long as -/// it's not used elsewhere. -/// \param the OpenGL texture object -/// -void ReleaseTexture(unsigned int idx) { - - // iterate through the textures to find the requested texture - std::map::iterator it=g_Textures.begin(); - for( ; it != g_Textures.end(); ++it ) { - - // when found... - if(idx == it->second.idx) { - - // if ref count is zero ... - if(--it->second.ref == 0) { - - // delete texture - glDeleteTextures(1,&(it->second.idx)); - g_Textures.erase(it); - } - return; - } - } -} - -//-------------------------------------------------------------------------- -/// \brief This function deletes all currently loaded textures -/// -void DeleteAllTextures() { - - std::map::iterator it=g_Textures.begin(); - for( ; it != g_Textures.end(); ++it ) { - glDeleteTextures(1,&it->second.idx); - } - g_Textures.clear(); -} - - -//-------------------------------------------------------------------------- -/// \brief This function returns the amount of memory used to store the -/// specified openGL texture. -/// \param idx - the openGL texture object -/// \return the amount of memory used (in bytes) -/// -unsigned int GetTextureSize(unsigned int idx) { - - // iterate through the textures to find the requested texture - std::map::iterator it=g_Textures.begin(); - for( ; it != g_Textures.end(); ++it ) { - - // when found... - if(idx == it->second.idx) { - - return it->second.size; - } - } - return 0; -} - -//-------------------------------------------------------------------------- -/// \brief This returns the amount of memory used to store all textures -/// currently loaded. The size returned is in bytes -/// \return the total amount of memory used (in bytes) -/// -unsigned int GetTotalTextureSize() { - unsigned int sz=0; - // iterate through the textures to find the requested texture - std::map::iterator it=g_Textures.begin(); - for( ; it != g_Textures.end(); ++it ) { - sz += it->second.size; - } - return sz; -} - diff --git a/src/projectM-engine/texture.h b/src/projectM-engine/texture.h deleted file mode 100644 index 05a50a7b3..000000000 --- a/src/projectM-engine/texture.h +++ /dev/null @@ -1,48 +0,0 @@ -//-------------------------------------------------------------------------- -/// \file texture.h -/// \author Rob Bateman [robthebloke@hotmail.com] -/// \date 7-3-2005 -/// \brief This header defines some functions -/// -//-------------------------------------------------------------------------- - -#ifndef __GL_TEXTURE__H__ -#define __GL_TEXTURE__H__ - -//-------------------------------------------------------------------------- -/// \brief This function loads the specified image file and returns the -/// openGL texture object for it. Only tga, bmp and pcx images are -/// supported. -/// \param filename - the image file to use for the texture -/// \return compressed - if true DXT compression will be used. -/// -unsigned int LoadTexture(const char* filename,bool compressed=true); - -//-------------------------------------------------------------------------- -/// \brief This function will release the specified texture (as long as -/// it's not used elsewhere. -/// \param the OpenGL texture object -/// -void ReleaseTexture(unsigned int); - -//-------------------------------------------------------------------------- -/// \brief This function deletes all currently loaded textures -/// -void DeleteAllTextures(); - -//-------------------------------------------------------------------------- -/// \brief This function returns the amount of memory used to store the -/// specified openGL texture. -/// \param idx - the openGL texture object -/// \return the amount of memory used (in bytes) -/// -unsigned int GetTextureSize(unsigned int); - -//-------------------------------------------------------------------------- -/// \brief This returns the amount of memory used to store all textures -/// currently loaded. The size returned is in bytes -/// \return the total amount of memory used (in bytes) -/// -unsigned int GetTotalTextureSize(); - -#endif diff --git a/src/projectM-engine/tgaLoader.c b/src/projectM-engine/tgaLoader.c deleted file mode 100644 index 8a7767188..000000000 --- a/src/projectM-engine/tgaLoader.c +++ /dev/null @@ -1,597 +0,0 @@ - - -#include -#include -#include -#include -#include - -#ifndef IN -#define IN -#endif - -#ifndef OUT -#define OUT -#endif - - - -/* - * most compilers tend to align structure data size so that its size is 4byte - * aligned (ie, divisable by 4). This happens due to compiler optimisations that we have - * to manually prevent. This can be done with the #pragma pack() directive. Without it, - * the struct would probably end up looking like : - * - * typedef struct - * { - * // bytes 0 to 3 - * unsigned char m_ImageIdent; - * unsigned char m_ColourMapType; - * unsigned char m_ImageType; - * - * unsigned char PADDING1; - * - * // bytes 4 to 7 - * unsigned short m_ColourMapOrigin; - * unsigned short m_ColourMapSize; - * - * // bytes 8 to 11 - * unsigned char m_ColourMapESize; - * - * unsigned char PADDING2; - * - * unsigned short m_Xorigin; - * - * // bytes 12 to 15 - * unsigned short m_Yorigin; - * unsigned short m_Width; - * - * // bytes 16 to 19 - * unsigned short m_Height; - * unsigned char m_PixelIndexSize; - * unsigned char m_ImageDescByte; - * } - * tga_header; - * - * This struct is 20 bytes in size, which is going to cause us some big problems when - * trying to read an 18 byte chunk from the file. We will end up with rubbish data. - * This is why we tell compilers to align data structures to one byte. - */ -#pragma pack(push,1) - -/* -* This structure is used to hold the header of the tga image file format -*/ -typedef struct -{ - unsigned char m_ImageIdent; /* size of image identification field */ - unsigned char m_ColourMapType; /* 0 - No colour map, 1 - has colour map */ - unsigned char m_ImageType; /* 0 - No Image, 1 - 8bit, 2 - uncompress RGB ..... */ - unsigned short m_ColourMapOrigin; /* Index of first colour map entry */ - unsigned short m_ColourMapSize; /* Number of colour map entries */ - unsigned char m_ColourMapESize; /* Number of bits for each entry (24) */ - unsigned short m_Xorigin; /* x origin of image */ - unsigned short m_Yorigin; /* y origin of image */ - unsigned short m_Width; /* width of image in pixels */ - unsigned short m_Height; /* height of image in pixels */ - unsigned char m_PixelIndexSize; /* number of bits stored in each pixel index */ - unsigned char m_ImageDescByte; /* should always be zero.... */ -} -tga_header; - - -/* - * This structure is used purely for reading the RLE files. - */ -typedef struct -{ - unsigned char Count : 7; - /* if these two bits are 1, then we have a start of a byte run */ - unsigned char Flag : 1; -} -TgaByte; - -/* -* reset structure alignment back to the default -*/ -#pragma pack(pop) - - -/* - * This union is a total of 2 bytes in size. s and b both share the - * same memory so the 2byte variable can be easily split into its constituant bytes. - * By assigning 1 to the 2byte variable, we can expect the array b to hold - * b[] = { 1, 0 } on a PC (LSB) and b[] = { 0, 1 } on an o2 (MSB). It also makes - * it nice and easy to swap the variables bytes. - */ -union charToShort -{ - /* 1 x 2 byte variable */ - unsigned short s; - /* 2 x 1 byte variables */ - unsigned char b[2]; -}; - - - -/* - * This function is used to determine if the system has LSB or MSB byte ordering. - * By using a union, both the 2byte variable(short) and the array of 2 individual - * bytes (chars) will be stored in the same memory location. ie, the charToShort - * union data type is only two bytes in size and allows you to split apart a 2byte - * variable to check the ordering. - * - * The two byte variables in the tga header will have to have their byte orderings - * switched on irix and mac, hence the use of this (sneaky) function to see if we need - * to swap the data bytes. - */ -int CheckByteOrder() -{ - /* Create a 2byte check variable */ - union charToShort cs; - - /* assign 1 to the 2byte variable */ - cs.s = 1; - - /* check the first byte */ - if(cs.b[0] == 0) - return 1; - - return 0; -} - - - -/* - * This function switches the byte orderings of two byte variables. It accepts a - * pointer to the memory location you wish to swap. - */ -void Swap2Bytes(IN void *ptr) -{ - /* assign a pointer to the 2byte variable */ - unsigned char *pcs = (unsigned char*)ptr; - - /* swap the bytes around */ - unsigned char temp = pcs[0]; - pcs[0] = pcs[1]; - pcs[1] = temp; -} - -/* - * \brief This function flips pixel colours from RGB (or RGBA) to BGR/BGRA - * \param data - the pixel data to flip - * \param num_px - the number of pixels to flip - * \param bpp - the bytes per pixel, ie 1,3,4 - */ -void BGR_TO_RGB(unsigned char* data,unsigned int num_px,unsigned int bpp) -{ - /* get pointer to data end */ - unsigned char *end = data + (bpp * num_px); - - /* loop till end of data, move on by number of bytes in pixel each time */ - for( ; data != end ; data += bpp ) - { - unsigned char temp = *data; - *data = data[2]; - data[2] = temp; - } -} - -/* - * \brief This function flips an image upside down... - * \param data - the pixel data to flip - * \param rowsize - the data size of each scanline - * \param height - the number of scanlines in the image - */ -void FlipDaImage( unsigned char* data, unsigned int rowsize,unsigned int height) -{ - unsigned int mid = height/2,i,j; - unsigned char *temprow = (unsigned char*) malloc ( rowsize ); - assert( temprow ); - - for( i=0, j=(height-1); i