159 | #endif
160 |
161 | #define STBI_VERSION 1
162 |
163 | enum
164 | {
165 | STBI_default = 0, // only used for req_comp
166 |
167 | STBI_grey = 1,
168 | STBI_grey_alpha = 2,
169 | STBI_rgb = 3,
170 | STBI_rgb_alpha = 4,
171 | };
172 |
173 | typedef unsigned char stbi_uc;
174 |
175 | #ifdef __cplusplus
176 | extern "C" {
177 | #endif
178 |
179 | // WRITING API
180 |
181 | #if !defined(STBI_NO_WRITE) && !defined(STBI_NO_STDIO)
182 | // write a BMP/TGA file given tightly packed 'comp' channels (no padding, nor bmp-stride-padding)
183 | // (you must include the appropriate extension in the filename).
184 | // returns TRUE on success, FALSE if couldn't open file, error writing file
185 | extern int stbi_write_bmp (char const *filename, int x, int y, int comp, void *data);
186 | extern int stbi_write_tga (char const *filename, int x, int y, int comp, void *data);
187 | #endif
188 |
189 | // PRIMARY API - works on images of any type
190 |
191 | // load image by filename, open file, or memory buffer
192 | #ifndef STBI_NO_STDIO
193 | extern stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp);
194 | extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
195 | extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp);
196 | #endif
197 | extern stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
198 | // for stbi_load_from_file, file pointer is left pointing immediately after image
199 |
200 | #ifndef STBI_NO_HDR
201 | #ifndef STBI_NO_STDIO
202 | extern float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp);
203 | extern float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
204 | #endif
205 | extern float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
206 |
207 | extern void stbi_hdr_to_ldr_gamma(float gamma);
208 | extern void stbi_hdr_to_ldr_scale(float scale);
209 |
210 | extern void stbi_ldr_to_hdr_gamma(float gamma);
211 | extern void stbi_ldr_to_hdr_scale(float scale);
212 |
213 | #endif // STBI_NO_HDR
214 |
215 | // get a VERY brief reason for failure
216 | // NOT THREADSAFE
217 | extern char *stbi_failure_reason (void);
218 |
219 | // free the loaded image -- this is just free()
220 | extern void stbi_image_free (void *retval_from_stbi_load);
221 |
222 | // get image dimensions & components without fully decoding
223 | extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
224 | extern int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
225 | #ifndef STBI_NO_STDIO
226 | extern int stbi_info (char const *filename, int *x, int *y, int *comp);
227 | extern int stbi_is_hdr (char const *filename);
228 | extern int stbi_is_hdr_from_file(FILE *f);
229 | #endif
230 |
231 | // ZLIB client - used by PNG, available for other purposes
232 |
233 | extern char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
234 | extern char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
235 | extern int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
236 |
237 | extern char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
238 | extern int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
239 |
240 | // TYPE-SPECIFIC ACCESS
241 |
242 | // is it a jpeg?
243 | extern int stbi_jpeg_test_memory (stbi_uc const *buffer, int len);
244 | extern stbi_uc *stbi_jpeg_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
245 | extern int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
246 |
247 | #ifndef STBI_NO_STDIO
248 | extern stbi_uc *stbi_jpeg_load (char const *filename, int *x, int *y, int *comp, int req_comp);
249 | extern int stbi_jpeg_test_file (FILE *f);
250 | extern stbi_uc *stbi_jpeg_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
251 |
252 | extern int stbi_jpeg_info (char const *filename, int *x, int *y, int *comp);
253 | extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp);
254 | #endif
255 |
256 | // is it a png?
257 | extern int stbi_png_test_memory (stbi_uc const *buffer, int len);
258 | extern stbi_uc *stbi_png_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
259 | extern int stbi_png_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp);
260 |
261 | #ifndef STBI_NO_STDIO
262 | extern stbi_uc *stbi_png_load (char const *filename, int *x, int *y, int *comp, int req_comp);
263 | extern int stbi_png_info (char const *filename, int *x, int *y, int *comp);
264 | extern int stbi_png_test_file (FILE *f);
265 | extern stbi_uc *stbi_png_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
266 | extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp);
267 | #endif
268 |
269 | // is it a bmp?
270 | extern int stbi_bmp_test_memory (stbi_uc const *buffer, int len);
271 |
272 | extern stbi_uc *stbi_bmp_load (char const *filename, int *x, int *y, int *comp, int req_comp);
273 | extern stbi_uc *stbi_bmp_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
274 | #ifndef STBI_NO_STDIO
275 | extern int stbi_bmp_test_file (FILE *f);
276 | extern stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
277 | #endif
278 |
279 | // is it a tga?
280 | extern int stbi_tga_test_memory (stbi_uc const *buffer, int len);
281 |
282 | extern stbi_uc *stbi_tga_load (char const *filename, int *x, int *y, int *comp, int req_comp);
283 | extern stbi_uc *stbi_tga_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
284 | #ifndef STBI_NO_STDIO
285 | extern int stbi_tga_test_file (FILE *f);
286 | extern stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
287 | #endif
288 |
289 | // is it a psd?
290 | extern int stbi_psd_test_memory (stbi_uc const *buffer, int len);
291 |
292 | extern stbi_uc *stbi_psd_load (char const *filename, int *x, int *y, int *comp, int req_comp);
293 | extern stbi_uc *stbi_psd_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
294 | #ifndef STBI_NO_STDIO
295 | extern int stbi_psd_test_file (FILE *f);
296 | extern stbi_uc *stbi_psd_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
297 | #endif
298 |
299 | // is it an hdr?
300 | extern int stbi_hdr_test_memory (stbi_uc const *buffer, int len);
301 |
302 | extern float * stbi_hdr_load (char const *filename, int *x, int *y, int *comp, int req_comp);
303 | extern float * stbi_hdr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
304 | extern stbi_uc *stbi_hdr_load_rgbe (char const *filename, int *x, int *y, int *comp, int req_comp);
305 | extern float * stbi_hdr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
306 | #ifndef STBI_NO_STDIO
307 | extern int stbi_hdr_test_file (FILE *f);
308 | extern float * stbi_hdr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
309 | extern stbi_uc *stbi_hdr_load_rgbe_file (FILE *f, int *x, int *y, int *comp, int req_comp);
310 | #endif
311 |
312 | // define new loaders
313 | typedef struct
314 | {
315 | int (*test_memory)(stbi_uc const *buffer, int len);
316 | stbi_uc * (*load_from_memory)(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
317 | #ifndef STBI_NO_STDIO
318 | int (*test_file)(FILE *f);
319 | stbi_uc * (*load_from_file)(FILE *f, int *x, int *y, int *comp, int req_comp);
320 | #endif
321 | } stbi_loader;
322 |
323 | // register a loader by filling out the above structure (you must defined ALL functions)
324 | // returns 1 if added or already added, 0 if not added (too many loaders)
325 | // NOT THREADSAFE
326 | extern int stbi_register_loader(stbi_loader *loader);
327 |
328 | // define faster low-level operations (typically SIMD support)
329 | #if STBI_SIMD
330 | typedef void (*stbi_idct_8x8)(uint8 *out, int out_stride, short data[64], unsigned short *dequantize);
331 | // compute an integer IDCT on "input"
332 | // input[x] = data[x] * dequantize[x]
333 | // write results to 'out': 64 samples, each run of 8 spaced by 'out_stride'
334 | // CLAMP results to 0..255
335 | typedef void (*stbi_YCbCr_to_RGB_run)(uint8 *output, uint8 const *y, uint8 const *cb, uint8 const *cr, int count, int step);
336 | // compute a conversion from YCbCr to RGB
337 | // 'count' pixels
338 | // write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B
339 | // y: Y input channel
340 | // cb: Cb input channel; scale/biased to be 0..255
341 | // cr: Cr input channel; scale/biased to be 0..255
342 |
343 | extern void stbi_install_idct(stbi_idct_8x8 func);
344 | extern void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func);
345 | #endif // STBI_SIMD
346 |
347 | #ifdef __cplusplus
348 | }
349 | #endif
350 |
351 | //
352 | //
353 | //// end header file /////////////////////////////////////////////////////
354 | #endif // STBI_INCLUDE_STB_IMAGE_H
355 |
--------------------------------------------------------------------------------
/soil.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | lonesock.net: SOIL
6 |
27 |
28 |
29 |
30 | Simple OpenGL Image Library
31 |
32 |
33 | Introduction:
SOIL is a tiny C library used primarily
34 | for uploading textures into OpenGL. It is based on stb_image version
35 | 1.16, the public domain code from Sean Barrett (found here).
36 | I have extended it to load TGA and DDS files, and to perform common
37 | functions needed in loading OpenGL textures. SOIL can also be used
38 | to save and load images in a variety of formats (useful for loading
39 | height maps, non-OpenGL applications, etc.)
40 |
41 | Download:
42 |
43 |
44 | You can grab the latest version of SOIL here.
45 | (July 7, 2008: see the change log at the bottom of this page.)
46 | You can also checkout the latest code from the new SVN repository, login as guest/guest:
47 | svn://www.twisted-works.com/jdummer/public/SOIL
48 | (thanks for the SVN hosting, Sherief!)
49 |
50 |
51 | License:
52 |
53 |
54 | Public Domain
55 |
56 |
57 | Features:
58 |
59 |
60 | - Readable Image Formats:
61 |
62 | - BMP - non-1bpp, non-RLE (from stb_image documentation)
63 |
- PNG - non-interlaced (from stb_image documentation)
64 |
- JPG - JPEG baseline (from stb_image documentation)
65 |
- TGA - greyscale or RGB or RGBA or indexed, uncompressed or RLE
66 |
- DDS - DXT1/2/3/4/5, uncompressed, cubemaps (can't read 3D DDS files yet)
67 |
- PSD - (from stb_image documentation)
68 |
- HDR - converted to LDR, unless loaded with *HDR* functions (RGBE or RGBdivA or RGBdivA2)
69 |
70 | - Writeable Image Formats:
71 |
72 | - TGA - Greyscale or RGB or RGBA, uncompressed
73 |
- BMP - RGB, uncompressed
74 |
- DDS - RGB as DXT1, or RGBA as DXT5
75 |
76 | - Can load an image file directly into a 2D OpenGL texture, optionally performing the following functions:
77 |
78 | - Can generate a new texture handle, or reuse one specified
79 |
- Can automatically rescale the image to the next largest power-of-two size
80 |
- Can automatically create MIPmaps
81 |
- Can scale (not simply clamp) the RGB values into the "safe range" for NTSC displays (16 to 235, as recommended here)
82 |
- Can multiply alpha on load (for more correct blending / compositing)
83 |
- Can flip the image vertically
84 |
- Can compress and upload any image as DXT1 or DXT5 (if EXT_texture_compression_s3tc is available), using an internal (very fast!) compressor
85 |
- Can convert the RGB to YCoCg color space (useful with DXT5 compression: see this link from NVIDIA)
86 |
- Will automatically downsize a texture if it is larger than GL_MAX_TEXTURE_SIZE
87 |
- Can directly upload DDS files (DXT1/3/5/uncompressed/cubemap, with or without MIPmaps). Note: directly uploading the compressed DDS image will disable the other options (no flipping, no pre-multiplying alpha, no rescaling, no creation of MIPmaps, no auto-downsizing)
88 |
- Can load rectangluar textures for GUI elements or splash screens (requires GL_ARB/EXT/NV_texture_rectangle)
89 |
90 | - Can decompress images from RAM (e.g. via PhysicsFS or similar) into an OpenGL texture (same features as regular 2D textures, above)
91 |
- Can load cube maps directly into an OpenGL texture (same features as regular 2D textures, above)
92 |
93 | - Can take six image files directly into an OpenGL cube map texture
94 |
- Can take a single image file where width = 6*height (or vice versa), split it into an OpenGL cube map texture
95 |
96 | - No external dependencies
97 |
- Tiny
98 |
- Cross platform (Windows, *nix, Mac OS X)
99 |
- Public Domain
100 |
101 |
102 | ToDo:
103 |
104 | - More testing
105 |
- add HDR functions to load from memory and load to RGBE unsigned char*
106 |
107 |
Usage:
SOIL is meant to be used as a static
108 | library (as it's tiny and in the public domain). You can use the static
109 | library file included in the zip (libSOIL.a works for MinGW and Microsoft
110 | compilers...feel free to rename it to SOIL.lib if that makes you happy),
111 | or compile the library yourself. The code is cross-platform and has been
112 | tested on Windows, Linux, and Mac. (The heaviest testing has been on the
113 | Windows platform, so feel free to email me if you find any issues with
114 | other platforms.)
115 |
Simply include SOIL.h in your C or C++ file,
116 | link in the static library, and then use any of SOIL's functions. The
117 | file SOIL.h contains simple doxygen style documentation. (If you use the
118 | static library, no other header files are needed besides SOIL.h) Below
119 | are some simple usage examples:
120 |
121 |
122 | /* load an image file directly as a new OpenGL texture */
123 | GLuint tex_2d = SOIL_load_OGL_texture
124 | (
125 | "img.png",
126 | SOIL_LOAD_AUTO,
127 | SOIL_CREATE_NEW_ID,
128 | SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB | SOIL_FLAG_COMPRESS_TO_DXT
129 | );
130 |
131 | /* check for an error during the load process */
132 | if( 0 == tex_2d )
133 | {
134 | printf( "SOIL loading error: '%s'\n", SOIL_last_result() );
135 | }
136 |
137 | /* load another image, but into the same texture ID, overwriting the last one */
138 | tex_2d = SOIL_load_OGL_texture
139 | (
140 | "some_other_img.dds",
141 | SOIL_LOAD_AUTO,
142 | tex_2d,
143 | SOIL_FLAG_DDS_LOAD_DIRECT
144 | );
145 |
146 | /* load 6 images into a new OpenGL cube map, forcing RGB */
147 | GLuint tex_cube = SOIL_load_OGL_cubemap
148 | (
149 | "xp.jpg",
150 | "xn.jpg",
151 | "yp.jpg",
152 | "yn.jpg",
153 | "zp.jpg",
154 | "zn.jpg",
155 | SOIL_LOAD_RGB,
156 | SOIL_CREATE_NEW_ID,
157 | SOIL_FLAG_MIPMAPS
158 | );
159 |
160 | /* load and split a single image into a new OpenGL cube map, default format */
161 | /* face order = East South West North Up Down => "ESWNUD", case sensitive! */
162 | GLuint single_tex_cube = SOIL_load_OGL_single_cubemap
163 | (
164 | "split_cubemap.png",
165 | "EWUDNS",
166 | SOIL_LOAD_AUTO,
167 | SOIL_CREATE_NEW_ID,
168 | SOIL_FLAG_MIPMAPS
169 | );
170 |
171 | /* actually, load a DDS cubemap over the last OpenGL cube map, default format */
172 | /* try to load it directly, but give the order of the faces in case that fails */
173 | /* the DDS cubemap face order is pre-defined as SOIL_DDS_CUBEMAP_FACE_ORDER */
174 | single_tex_cube = SOIL_load_OGL_single_cubemap
175 | (
176 | "overwrite_cubemap.dds",
177 | SOIL_DDS_CUBEMAP_FACE_ORDER,
178 | SOIL_LOAD_AUTO,
179 | single_tex_cube,
180 | SOIL_FLAG_MIPMAPS | SOIL_FLAG_DDS_LOAD_DIRECT
181 | );
182 |
183 | /* load an image as a heightmap, forcing greyscale (so channels should be 1) */
184 | int width, height, channels;
185 | unsigned char *ht_map = SOIL_load_image
186 | (
187 | "terrain.tga",
188 | &width, &height, &channels,
189 | SOIL_LOAD_L
190 | );
191 |
192 | /* save that image as another type */
193 | int save_result = SOIL_save_image
194 | (
195 | "new_terrain.dds",
196 | SOIL_SAVE_TYPE_DDS,
197 | width, height, channels,
198 | ht_map
199 | );
200 |
201 | /* save a screenshot of your awesome OpenGL game engine, running at 1024x768 */
202 | save_result = SOIL_save_screenshot
203 | (
204 | "awesomenessity.bmp",
205 | SOIL_SAVE_TYPE_BMP,
206 | 0, 0, 1024, 768
207 | );
208 |
209 | /* loaded a file via PhysicsFS, need to decompress the image from RAM, */
210 | /* where it's in a buffer: unsigned char *image_in_RAM */
211 | GLuint tex_2d_from_RAM = SOIL_load_OGL_texture_from_memory
212 | (
213 | image_in_RAM,
214 | image_in_RAM_bytes,
215 | SOIL_LOAD_AUTO,
216 | SOIL_CREATE_NEW_ID,
217 | SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_COMPRESS_TO_DXT
218 | );
219 |
220 | /* done with the heightmap, free up the RAM */
221 | SOIL_free_image_data( ht_map );
222 |
223 |
224 |
225 | Change Log:
226 |
227 | - July 7, 2008
228 |
229 | - upgraded to stb_image 1.16 (threadsafe! loads PSD and HDR formats)
230 |
- removed inline keyword from native SOIL functions (thanks Sherief, Boder, Amnesiac5!)
231 |
- added SOIL_load_OGL_HDR_texture (loads a Radience HDR file into RGBE, RGB/a, RGB/A^2)
232 |
- fixed a potential bug loading DDS files with a filename
233 |
- added a VC9 project file (thanks Sherief!)
234 |
235 | - November 10, 2007: added SOIL_FLAG_TEXTURE_RECTANGLE (pixel addressed non POT, useful for GUI, splash screens, etc.). Not useful with cubemaps, and disables repeating and MIPmaps.
236 |
- November 8, 2007
237 |
238 | - upgraded to stb_image 1.07
239 |
- fixed some includes and defines for compiling on OS X (thanks Mogui and swiftcoder!)
240 |
241 | - October 30, 2007
242 |
243 | - upgraded to stb_image 1.04, some tiny bug fixes
244 |
- there is now a makefile (under projects) for ease of building under Linux (thanks D J Peters!)
245 |
- Visual Studio 6/2003/2005 projects are working again
246 |
- patched SOIL for better pointer handling of the glCompressedTexImage2D extension (thanks Peter Sperl!)
247 |
- fixed DDS loading when force_channels=4 but there was no alpha; it was returning 3 channels. (Thanks LaurentGom!)
248 |
- fixed a bunch of channel issues in general. (Thanks Sean Barrett!)
249 |
250 | - October 27, 2007
251 |
252 | - correctly reports when there is no OpenGL context (thanks Merick Zero!)
253 |
- upgraded to stb_image 1.03 with support for loading the HDR image format
254 |
- fixed loading JPEG images while forcing the number of channels (e.g. to RGBA)
255 |
- changed SOIL_DDS_CUBEMAP_FACE_ORDER to a #define (thanks Dancho!)
256 |
- reorganized my additions to stb_image (you can define STBI_NO_DDS to compile SOIL without DDS support)
257 |
- added SOIL_FLAG_CoCg_Y, will convert RGB or RGBA to YCoCg color space (link)
258 |
259 | - October 5, 2007
260 |
261 | - added SOIL_FLAG_NTSC_SAFE_RGB
262 |
- bugfixed & optimized up_scale_image (used with SOIL_FLAG_POWER_OF_TWO and SOIL_FLAG_MIPMAPS)
263 |
264 | - September 20, 2007
265 |
266 | - upgraded to stb_image 1.0
267 |
- added the DXT source files to the MSVS projects
268 |
- removed sqrtf() calls (VS2k3 could not handle them)
269 |
- distributing only 1 library file (libSOIL.a, compiled with MinGW 4.2.1 tech preview!) for all windows compilers
270 |
- added an example of the *_from_memory() functions to the Usage section
271 |
272 | - September 6, 2007
273 |
274 | - added a slew of SOIL_load_*_from_memory() functions for people using PhysicsFS or similar
275 |
- more robust loading of non-compliant DDS files (thanks Dan!)
276 |
277 | - September 1, 2007 - fixed bugs from the last update [8^)
278 |
- August 31, 2007
279 |
280 | - can load uncompressed and cubemap DDS files
281 |
- can create a cubemap texture from a single (stitched) image file of any type
282 |
- sped up the image resizing code
283 |
284 | - August 24, 2007 - updated the documentation examples (at the bottom of this page)
285 |
- August 22, 2007
286 |
287 | - can load cube maps (needs serious testing)
288 |
- can compress 1- or 2-channel images to DXT1/5
289 |
- fixed some malloc() casts
290 |
- fixed C++ style comments
291 |
- fixed includes to compile under *nix or Mac (hopefully, needs testing...any volunteers?)
292 |
293 | - August 16, 2007
294 |
295 | - Will now downsize the image if necessary to fit GL_MAX_TEXTURE_SIZE
296 |
- added SOIL_create_OGL_texture() to upload raw image data that isn't from an image file
297 |
298 | - August 14, 2007 (PM) - Can now load indexed TGA
299 |
- August 14, 2007 (AM)
300 |
301 | - Updated to stb_image 0.97
302 |
- added result messages
303 |
- can now decompress DDS files (DXT1/2/3/4/5)
304 |
305 | - August 11, 2007 - MIPmaps can now handle non-square textures
306 |
- August 7, 2007
307 |
308 | - Can directly upload DXT1/3/5 DDS files (with or w/o MIPmaps)
309 |
- can compress any image to DXT1/5 (using a new & fast & simple compression scheme) and upload
310 |
- can save as DDS
311 |
312 | - July 31, 2007 - added compressing to DXT and flipping about Y
313 |
- July 30, 2007 - initial release
314 |
315 |
316 |
317 | back to
318 | www.lonesock.net
319 |
320 |
--------------------------------------------------------------------------------
/src/image_DXT.c:
--------------------------------------------------------------------------------
1 | /*
2 | Jonathan Dummer
3 | 2007-07-31-10.32
4 |
5 | simple DXT compression / decompression code
6 |
7 | public domain
8 | */
9 |
10 | #include "image_DXT.h"
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | /* set this =1 if you want to use the covarince matrix method...
17 | which is better than my method of using standard deviations
18 | overall, except on the infintesimal chance that the power
19 | method fails for finding the largest eigenvector */
20 | #define USE_COV_MAT 1
21 |
22 | /********* Function Prototypes *********/
23 | /*
24 | Takes a 4x4 block of pixels and compresses it into 8 bytes
25 | in DXT1 format (color only, no alpha). Speed is valued
26 | over prettyness, at least for now.
27 | */
28 | void compress_DDS_color_block(
29 | int channels,
30 | const unsigned char *const uncompressed,
31 | unsigned char compressed[8] );
32 | /*
33 | Takes a 4x4 block of pixels and compresses the alpha
34 | component it into 8 bytes for use in DXT5 DDS files.
35 | Speed is valued over prettyness, at least for now.
36 | */
37 | void compress_DDS_alpha_block(
38 | const unsigned char *const uncompressed,
39 | unsigned char compressed[8] );
40 |
41 | /********* Actual Exposed Functions *********/
42 | int
43 | save_image_as_DDS
44 | (
45 | const char *filename,
46 | int width, int height, int channels,
47 | const unsigned char *const data
48 | )
49 | {
50 | /* variables */
51 | FILE *fout;
52 | unsigned char *DDS_data;
53 | DDS_header header;
54 | int DDS_size;
55 | /* error check */
56 | if( (NULL == filename) ||
57 | (width < 1) || (height < 1) ||
58 | (channels < 1) || (channels > 4) ||
59 | (data == NULL ) )
60 | {
61 | return 0;
62 | }
63 | /* Convert the image */
64 | if( (channels & 1) == 1 )
65 | {
66 | /* no alpha, just use DXT1 */
67 | DDS_data = convert_image_to_DXT1( data, width, height, channels, &DDS_size );
68 | } else
69 | {
70 | /* has alpha, so use DXT5 */
71 | DDS_data = convert_image_to_DXT5( data, width, height, channels, &DDS_size );
72 | }
73 | /* save it */
74 | memset( &header, 0, sizeof( DDS_header ) );
75 | header.dwMagic = ('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24);
76 | header.dwSize = 124;
77 | header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE;
78 | header.dwWidth = width;
79 | header.dwHeight = height;
80 | header.dwPitchOrLinearSize = DDS_size;
81 | header.sPixelFormat.dwSize = 32;
82 | header.sPixelFormat.dwFlags = DDPF_FOURCC;
83 | if( (channels & 1) == 1 )
84 | {
85 | header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('1' << 24);
86 | } else
87 | {
88 | header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('5' << 24);
89 | }
90 | header.sCaps.dwCaps1 = DDSCAPS_TEXTURE;
91 | /* write it out */
92 | fout = fopen( filename, "wb");
93 | fwrite( &header, sizeof( DDS_header ), 1, fout );
94 | fwrite( DDS_data, 1, DDS_size, fout );
95 | fclose( fout );
96 | /* done */
97 | free( DDS_data );
98 | return 1;
99 | }
100 |
101 | unsigned char* convert_image_to_DXT1(
102 | const unsigned char *const uncompressed,
103 | int width, int height, int channels,
104 | int *out_size )
105 | {
106 | unsigned char *compressed;
107 | int i, j, x, y;
108 | unsigned char ublock[16*3];
109 | unsigned char cblock[8];
110 | int index = 0, chan_step = 1;
111 | int block_count = 0;
112 | /* error check */
113 | *out_size = 0;
114 | if( (width < 1) || (height < 1) ||
115 | (NULL == uncompressed) ||
116 | (channels < 1) || (channels > 4) )
117 | {
118 | return NULL;
119 | }
120 | /* for channels == 1 or 2, I do not step forward for R,G,B values */
121 | if( channels < 3 )
122 | {
123 | chan_step = 0;
124 | }
125 | /* get the RAM for the compressed image
126 | (8 bytes per 4x4 pixel block) */
127 | *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 8;
128 | compressed = (unsigned char*)malloc( *out_size );
129 | /* go through each block */
130 | for( j = 0; j < height; j += 4 )
131 | {
132 | for( i = 0; i < width; i += 4 )
133 | {
134 | /* copy this block into a new one */
135 | int idx = 0;
136 | int mx = 4, my = 4;
137 | if( j+4 >= height )
138 | {
139 | my = height - j;
140 | }
141 | if( i+4 >= width )
142 | {
143 | mx = width - i;
144 | }
145 | for( y = 0; y < my; ++y )
146 | {
147 | for( x = 0; x < mx; ++x )
148 | {
149 | ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels];
150 | ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step];
151 | ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step];
152 | }
153 | for( x = mx; x < 4; ++x )
154 | {
155 | ublock[idx++] = ublock[0];
156 | ublock[idx++] = ublock[1];
157 | ublock[idx++] = ublock[2];
158 | }
159 | }
160 | for( y = my; y < 4; ++y )
161 | {
162 | for( x = 0; x < 4; ++x )
163 | {
164 | ublock[idx++] = ublock[0];
165 | ublock[idx++] = ublock[1];
166 | ublock[idx++] = ublock[2];
167 | }
168 | }
169 | /* compress the block */
170 | ++block_count;
171 | compress_DDS_color_block( 3, ublock, cblock );
172 | /* copy the data from the block into the main block */
173 | for( x = 0; x < 8; ++x )
174 | {
175 | compressed[index++] = cblock[x];
176 | }
177 | }
178 | }
179 | return compressed;
180 | }
181 |
182 | unsigned char* convert_image_to_DXT5(
183 | const unsigned char *const uncompressed,
184 | int width, int height, int channels,
185 | int *out_size )
186 | {
187 | unsigned char *compressed;
188 | int i, j, x, y;
189 | unsigned char ublock[16*4];
190 | unsigned char cblock[8];
191 | int index = 0, chan_step = 1;
192 | int block_count = 0, has_alpha;
193 | /* error check */
194 | *out_size = 0;
195 | if( (width < 1) || (height < 1) ||
196 | (NULL == uncompressed) ||
197 | (channels < 1) || ( channels > 4) )
198 | {
199 | return NULL;
200 | }
201 | /* for channels == 1 or 2, I do not step forward for R,G,B vales */
202 | if( channels < 3 )
203 | {
204 | chan_step = 0;
205 | }
206 | /* # channels = 1 or 3 have no alpha, 2 & 4 do have alpha */
207 | has_alpha = 1 - (channels & 1);
208 | /* get the RAM for the compressed image
209 | (16 bytes per 4x4 pixel block) */
210 | *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 16;
211 | compressed = (unsigned char*)malloc( *out_size );
212 | /* go through each block */
213 | for( j = 0; j < height; j += 4 )
214 | {
215 | for( i = 0; i < width; i += 4 )
216 | {
217 | /* local variables, and my block counter */
218 | int idx = 0;
219 | int mx = 4, my = 4;
220 | if( j+4 >= height )
221 | {
222 | my = height - j;
223 | }
224 | if( i+4 >= width )
225 | {
226 | mx = width - i;
227 | }
228 | for( y = 0; y < my; ++y )
229 | {
230 | for( x = 0; x < mx; ++x )
231 | {
232 | ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels];
233 | ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step];
234 | ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step];
235 | ublock[idx++] =
236 | has_alpha * uncompressed[(j+y)*width*channels+(i+x)*channels+channels-1]
237 | + (1-has_alpha)*255;
238 | }
239 | for( x = mx; x < 4; ++x )
240 | {
241 | ublock[idx++] = ublock[0];
242 | ublock[idx++] = ublock[1];
243 | ublock[idx++] = ublock[2];
244 | ublock[idx++] = ublock[3];
245 | }
246 | }
247 | for( y = my; y < 4; ++y )
248 | {
249 | for( x = 0; x < 4; ++x )
250 | {
251 | ublock[idx++] = ublock[0];
252 | ublock[idx++] = ublock[1];
253 | ublock[idx++] = ublock[2];
254 | ublock[idx++] = ublock[3];
255 | }
256 | }
257 | /* now compress the alpha block */
258 | compress_DDS_alpha_block( ublock, cblock );
259 | /* copy the data from the compressed alpha block into the main buffer */
260 | for( x = 0; x < 8; ++x )
261 | {
262 | compressed[index++] = cblock[x];
263 | }
264 | /* then compress the color block */
265 | ++block_count;
266 | compress_DDS_color_block( 4, ublock, cblock );
267 | /* copy the data from the compressed color block into the main buffer */
268 | for( x = 0; x < 8; ++x )
269 | {
270 | compressed[index++] = cblock[x];
271 | }
272 | }
273 | }
274 | return compressed;
275 | }
276 |
277 | /********* Helper Functions *********/
278 | int convert_bit_range( int c, int from_bits, int to_bits )
279 | {
280 | int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1);
281 | return (b + (b >> from_bits)) >> from_bits;
282 | }
283 |
284 | int rgb_to_565( int r, int g, int b )
285 | {
286 | return
287 | (convert_bit_range( r, 8, 5 ) << 11) |
288 | (convert_bit_range( g, 8, 6 ) << 05) |
289 | (convert_bit_range( b, 8, 5 ) << 00);
290 | }
291 |
292 | void rgb_888_from_565( unsigned int c, int *r, int *g, int *b )
293 | {
294 | *r = convert_bit_range( (c >> 11) & 31, 5, 8 );
295 | *g = convert_bit_range( (c >> 05) & 63, 6, 8 );
296 | *b = convert_bit_range( (c >> 00) & 31, 5, 8 );
297 | }
298 |
299 | void compute_color_line_STDEV(
300 | const unsigned char *const uncompressed,
301 | int channels,
302 | float point[3], float direction[3] )
303 | {
304 | const float inv_16 = 1.0f / 16.0f;
305 | int i;
306 | float sum_r = 0.0f, sum_g = 0.0f, sum_b = 0.0f;
307 | float sum_rr = 0.0f, sum_gg = 0.0f, sum_bb = 0.0f;
308 | float sum_rg = 0.0f, sum_rb = 0.0f, sum_gb = 0.0f;
309 | /* calculate all data needed for the covariance matrix
310 | ( to compare with _rygdxt code) */
311 | for( i = 0; i < 16*channels; i += channels )
312 | {
313 | sum_r += uncompressed[i+0];
314 | sum_rr += uncompressed[i+0] * uncompressed[i+0];
315 | sum_g += uncompressed[i+1];
316 | sum_gg += uncompressed[i+1] * uncompressed[i+1];
317 | sum_b += uncompressed[i+2];
318 | sum_bb += uncompressed[i+2] * uncompressed[i+2];
319 | sum_rg += uncompressed[i+0] * uncompressed[i+1];
320 | sum_rb += uncompressed[i+0] * uncompressed[i+2];
321 | sum_gb += uncompressed[i+1] * uncompressed[i+2];
322 | }
323 | /* convert the sums to averages */
324 | sum_r *= inv_16;
325 | sum_g *= inv_16;
326 | sum_b *= inv_16;
327 | /* and convert the squares to the squares of the value - avg_value */
328 | sum_rr -= 16.0f * sum_r * sum_r;
329 | sum_gg -= 16.0f * sum_g * sum_g;
330 | sum_bb -= 16.0f * sum_b * sum_b;
331 | sum_rg -= 16.0f * sum_r * sum_g;
332 | sum_rb -= 16.0f * sum_r * sum_b;
333 | sum_gb -= 16.0f * sum_g * sum_b;
334 | /* the point on the color line is the average */
335 | point[0] = sum_r;
336 | point[1] = sum_g;
337 | point[2] = sum_b;
338 | #if USE_COV_MAT
339 | /*
340 | The following idea was from ryg.
341 | (https://mollyrocket.com/forums/viewtopic.php?t=392)
342 | The method worked great (less RMSE than mine) most of
343 | the time, but had some issues handling some simple
344 | boundary cases, like full green next to full red,
345 | which would generate a covariance matrix like this:
346 |
347 | | 1 -1 0 |
348 | | -1 1 0 |
349 | | 0 0 0 |
350 |
351 | For a given starting vector, the power method can
352 | generate all zeros! So no starting with {1,1,1}
353 | as I was doing! This kind of error is still a
354 | slight posibillity, but will be very rare.
355 | */
356 | /* use the covariance matrix directly
357 | (1st iteration, don't use all 1.0 values!) */
358 | sum_r = 1.0f;
359 | sum_g = 2.718281828f;
360 | sum_b = 3.141592654f;
361 | direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb;
362 | direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb;
363 | direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb;
364 | /* 2nd iteration, use results from the 1st guy */
365 | sum_r = direction[0];
366 | sum_g = direction[1];
367 | sum_b = direction[2];
368 | direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb;
369 | direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb;
370 | direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb;
371 | /* 3rd iteration, use results from the 2nd guy */
372 | sum_r = direction[0];
373 | sum_g = direction[1];
374 | sum_b = direction[2];
375 | direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb;
376 | direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb;
377 | direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb;
378 | #else
379 | /* use my standard deviation method
380 | (very robust, a tiny bit slower and less accurate) */
381 | direction[0] = sqrt( sum_rr );
382 | direction[1] = sqrt( sum_gg );
383 | direction[2] = sqrt( sum_bb );
384 | /* which has a greater component */
385 | if( sum_gg > sum_rr )
386 | {
387 | /* green has greater component, so base the other signs off of green */
388 | if( sum_rg < 0.0f )
389 | {
390 | direction[0] = -direction[0];
391 | }
392 | if( sum_gb < 0.0f )
393 | {
394 | direction[2] = -direction[2];
395 | }
396 | } else
397 | {
398 | /* red has a greater component */
399 | if( sum_rg < 0.0f )
400 | {
401 | direction[1] = -direction[1];
402 | }
403 | if( sum_rb < 0.0f )
404 | {
405 | direction[2] = -direction[2];
406 | }
407 | }
408 | #endif
409 | }
410 |
411 | void LSE_master_colors_max_min(
412 | int *cmax, int *cmin,
413 | int channels,
414 | const unsigned char *const uncompressed )
415 | {
416 | int i, j;
417 | /* the master colors */
418 | int c0[3], c1[3];
419 | /* used for fitting the line */
420 | float sum_x[] = { 0.0f, 0.0f, 0.0f };
421 | float sum_x2[] = { 0.0f, 0.0f, 0.0f };
422 | float dot_max = 1.0f, dot_min = -1.0f;
423 | float vec_len2 = 0.0f;
424 | float dot;
425 | /* error check */
426 | if( (channels < 3) || (channels > 4) )
427 | {
428 | return;
429 | }
430 | compute_color_line_STDEV( uncompressed, channels, sum_x, sum_x2 );
431 | vec_len2 = 1.0f / ( 0.00001f +
432 | sum_x2[0]*sum_x2[0] + sum_x2[1]*sum_x2[1] + sum_x2[2]*sum_x2[2] );
433 | /* finding the max and min vector values */
434 | dot_max =
435 | (
436 | sum_x2[0] * uncompressed[0] +
437 | sum_x2[1] * uncompressed[1] +
438 | sum_x2[2] * uncompressed[2]
439 | );
440 | dot_min = dot_max;
441 | for( i = 1; i < 16; ++i )
442 | {
443 | dot =
444 | (
445 | sum_x2[0] * uncompressed[i*channels+0] +
446 | sum_x2[1] * uncompressed[i*channels+1] +
447 | sum_x2[2] * uncompressed[i*channels+2]
448 | );
449 | if( dot < dot_min )
450 | {
451 | dot_min = dot;
452 | } else if( dot > dot_max )
453 | {
454 | dot_max = dot;
455 | }
456 | }
457 | /* and the offset (from the average location) */
458 | dot = sum_x2[0]*sum_x[0] + sum_x2[1]*sum_x[1] + sum_x2[2]*sum_x[2];
459 | dot_min -= dot;
460 | dot_max -= dot;
461 | /* post multiply by the scaling factor */
462 | dot_min *= vec_len2;
463 | dot_max *= vec_len2;
464 | /* OK, build the master colors */
465 | for( i = 0; i < 3; ++i )
466 | {
467 | /* color 0 */
468 | c0[i] = (int)(0.5f + sum_x[i] + dot_max * sum_x2[i]);
469 | if( c0[i] < 0 )
470 | {
471 | c0[i] = 0;
472 | } else if( c0[i] > 255 )
473 | {
474 | c0[i] = 255;
475 | }
476 | /* color 1 */
477 | c1[i] = (int)(0.5f + sum_x[i] + dot_min * sum_x2[i]);
478 | if( c1[i] < 0 )
479 | {
480 | c1[i] = 0;
481 | } else if( c1[i] > 255 )
482 | {
483 | c1[i] = 255;
484 | }
485 | }
486 | /* down_sample (with rounding?) */
487 | i = rgb_to_565( c0[0], c0[1], c0[2] );
488 | j = rgb_to_565( c1[0], c1[1], c1[2] );
489 | if( i > j )
490 | {
491 | *cmax = i;
492 | *cmin = j;
493 | } else
494 | {
495 | *cmax = j;
496 | *cmin = i;
497 | }
498 | }
499 |
500 | void
501 | compress_DDS_color_block
502 | (
503 | int channels,
504 | const unsigned char *const uncompressed,
505 | unsigned char compressed[8]
506 | )
507 | {
508 | /* variables */
509 | int i;
510 | int next_bit;
511 | int enc_c0, enc_c1;
512 | int c0[4], c1[4];
513 | float color_line[] = { 0.0f, 0.0f, 0.0f, 0.0f };
514 | float vec_len2 = 0.0f, dot_offset = 0.0f;
515 | /* stupid order */
516 | int swizzle4[] = { 0, 2, 3, 1 };
517 | /* get the master colors */
518 | LSE_master_colors_max_min( &enc_c0, &enc_c1, channels, uncompressed );
519 | /* store the 565 color 0 and color 1 */
520 | compressed[0] = (enc_c0 >> 0) & 255;
521 | compressed[1] = (enc_c0 >> 8) & 255;
522 | compressed[2] = (enc_c1 >> 0) & 255;
523 | compressed[3] = (enc_c1 >> 8) & 255;
524 | /* zero out the compressed data */
525 | compressed[4] = 0;
526 | compressed[5] = 0;
527 | compressed[6] = 0;
528 | compressed[7] = 0;
529 | /* reconstitute the master color vectors */
530 | rgb_888_from_565( enc_c0, &c0[0], &c0[1], &c0[2] );
531 | rgb_888_from_565( enc_c1, &c1[0], &c1[1], &c1[2] );
532 | /* the new vector */
533 | vec_len2 = 0.0f;
534 | for( i = 0; i < 3; ++i )
535 | {
536 | color_line[i] = (float)(c1[i] - c0[i]);
537 | vec_len2 += color_line[i] * color_line[i];
538 | }
539 | if( vec_len2 > 0.0f )
540 | {
541 | vec_len2 = 1.0f / vec_len2;
542 | }
543 | /* pre-proform the scaling */
544 | color_line[0] *= vec_len2;
545 | color_line[1] *= vec_len2;
546 | color_line[2] *= vec_len2;
547 | /* compute the offset (constant) portion of the dot product */
548 | dot_offset = color_line[0]*c0[0] + color_line[1]*c0[1] + color_line[2]*c0[2];
549 | /* store the rest of the bits */
550 | next_bit = 8*4;
551 | for( i = 0; i < 16; ++i )
552 | {
553 | /* find the dot product of this color, to place it on the line
554 | (should be [-1,1]) */
555 | int next_value = 0;
556 | float dot_product =
557 | color_line[0] * uncompressed[i*channels+0] +
558 | color_line[1] * uncompressed[i*channels+1] +
559 | color_line[2] * uncompressed[i*channels+2] -
560 | dot_offset;
561 | /* map to [0,3] */
562 | next_value = (int)( dot_product * 3.0f + 0.5f );
563 | if( next_value > 3 )
564 | {
565 | next_value = 3;
566 | } else if( next_value < 0 )
567 | {
568 | next_value = 0;
569 | }
570 | /* OK, store this value */
571 | compressed[next_bit >> 3] |= swizzle4[ next_value ] << (next_bit & 7);
572 | next_bit += 2;
573 | }
574 | /* done compressing to DXT1 */
575 | }
576 |
577 | void
578 | compress_DDS_alpha_block
579 | (
580 | const unsigned char *const uncompressed,
581 | unsigned char compressed[8]
582 | )
583 | {
584 | /* variables */
585 | int i;
586 | int next_bit;
587 | int a0, a1;
588 | float scale_me;
589 | /* stupid order */
590 | int swizzle8[] = { 1, 7, 6, 5, 4, 3, 2, 0 };
591 | /* get the alpha limits (a0 > a1) */
592 | a0 = a1 = uncompressed[3];
593 | for( i = 4+3; i < 16*4; i += 4 )
594 | {
595 | if( uncompressed[i] > a0 )
596 | {
597 | a0 = uncompressed[i];
598 | } else if( uncompressed[i] < a1 )
599 | {
600 | a1 = uncompressed[i];
601 | }
602 | }
603 | /* store those limits, and zero the rest of the compressed dataset */
604 | compressed[0] = a0;
605 | compressed[1] = a1;
606 | /* zero out the compressed data */
607 | compressed[2] = 0;
608 | compressed[3] = 0;
609 | compressed[4] = 0;
610 | compressed[5] = 0;
611 | compressed[6] = 0;
612 | compressed[7] = 0;
613 | /* store the all of the alpha values */
614 | next_bit = 8*2;
615 | scale_me = 7.9999f / (a0 - a1);
616 | for( i = 3; i < 16*4; i += 4 )
617 | {
618 | /* convert this alpha value to a 3 bit number */
619 | int svalue;
620 | int value = (int)((uncompressed[i] - a1) * scale_me);
621 | svalue = swizzle8[ value&7 ];
622 | /* OK, store this value, start with the 1st byte */
623 | compressed[next_bit >> 3] |= svalue << (next_bit & 7);
624 | if( (next_bit & 7) > 5 )
625 | {
626 | /* spans 2 bytes, fill in the start of the 2nd byte */
627 | compressed[1 + (next_bit >> 3)] |= svalue >> (8 - (next_bit & 7) );
628 | }
629 | next_bit += 3;
630 | }
631 | /* done compressing to DXT1 */
632 | }
633 |
--------------------------------------------------------------------------------