Major API revision
[convexer.git] / nbvtf / nbvtf.h
diff --git a/nbvtf/nbvtf.h b/nbvtf/nbvtf.h
new file mode 100644 (file)
index 0000000..c0071c9
--- /dev/null
@@ -0,0 +1,811 @@
+// nbvtf.h - v1.03 - Writer for Valve Texture Format - public domain
+// Written by Harry 'hgn' Godden
+//
+// Credits:
+//   Rich Geldreich - bc7enc (BC1/BC3 High Quality texture compression)
+//   Fabian "ryg" Giesen, stb - stb_dxt (BC1/BC3 realtime compressors)
+//   Sean Barrett - stb_image.h, stb_image_write.h (Image I/O)
+//
+// Note:
+//   This library assumes normal maps are input in OpenGL(correct) format, and will be converted into DirectX(incorrect) at
+//   compile time automatically. Do not submit DirectX normals into this software.
+//
+// Since this project uses stb_image, use '#define STB_IMAGE_IMPLEMENTATION' before including
+// Additionally, to use high quality DXT, '#define USE_LIBRGBCX'
+//
+// USAGE:
+//   Call these functions:
+//     int nbvtf_convert( const char *src, int w, int h, int mipmap, EImageFormat_t format, uint32_t usr_flags, const char *dest );
+//     int nbvtf_write( uint8_t *src, int w, int h, int mipmap, EImageFormat_t format, uint32_t usr_flags, const char *dest );
+//
+// Param definitions:
+//   src - RGBA byte data of image
+//   w - width of image
+//   h - height of image
+//   mipmap - enable mipmap generation (box filter), 1/0
+//   format - Choose from: k_EImageFormat_DXT1, compressedk_EImageFormat_DXT5, k_EImageFormat_BGR888, k_EImageFormat_ABGR8888
+//   usr_flags - You can append any flags but only really need TEXTUREFLAGS_NORMAL if texture is normal map
+//   dest - file path to write vtf to
+//   qual - Image quality 0-18 (rgbcx, stb always highqual)
+//  
+// Convert specific:
+//   src - file path of source image to convert
+//   w, h - MAXIMUM image dimentions of final product. Set as 0 to be automatic
+//
+// version history:
+//   v1.03  - Added quality switch
+//   v1.02  - Improved box filtering, small bug fixes
+//   v1.01  - switch to OpenGL normal format for input
+//   v1.00  - (hgn) first release
+//
+// LICENSE
+//   See end of file for license information.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NBVTF_MAX_MIPLEVELS 9
+#define nbvtf__min(a,b) (((a)<(b))?(a):(b))
+#define nbvtf__max(a,b) (((a)>(b))?(a):(b))
+
+#ifdef NBVTF_AS_SO
+ #include <stdio.h>
+ #include <string.h>
+ #include <stdint.h>
+ #include <math.h>
+ #define NBVTF_SHOW_STDERR
+ #define STB_IMAGE_IMPLEMENTATION
+#endif
+
+#include "stb/stb_image.h"
+
+#ifdef USE_LIBRGBCX
+ #include "librgbcx.h"
+#else
+ #define STB_DXT_IMPLEMENTATION
+ #include "stb/stb_dxt.h"
+#endif
+
+#ifdef NBVTF_SHOW_STDERR
+ #define NBVTF_ERR(...)
+#else
+ #define NBVTF_ERR(...) fprintf( stderr, __VA_ARGS__ )
+#endif
+
+#pragma pack(push, 1)
+
+typedef enum EImageFormat
+{
+       // Name                                                                 // Supported?
+       k_EImageFormat_NONE = -1,
+       k_EImageFormat_RGBA8888 = 0,            // YES
+       k_EImageFormat_ABGR8888, 
+       k_EImageFormat_RGB888,                          // YES
+       k_EImageFormat_BGR888,
+       k_EImageFormat_RGB565,
+       k_EImageFormat_I8,
+       k_EImageFormat_IA88,
+       k_EImageFormat_P8,
+       k_EImageFormat_A8,
+       k_EImageFormat_RGB888_BLUESCREEN,
+       k_EImageFormat_BGR888_BLUESCREEN,
+       k_EImageFormat_ARGB8888,
+       k_EImageFormat_BGRA8888,
+       k_EImageFormat_DXT1,                                    // YES
+       k_EImageFormat_DXT3,
+       k_EImageFormat_DXT5,                                    // YES
+       k_EImageFormat_BGRX8888,
+       k_EImageFormat_BGR565,
+       k_EImageFormat_BGRX5551,
+       k_EImageFormat_BGRA4444,
+       k_EImageFormat_DXT1_ONEBITALPHA,
+       k_EImageFormat_BGRA5551,
+       k_EImageFormat_UV88,
+       k_EImageFormat_UVWQ8888,
+       k_EImageFormat_RGBA16161616F,
+       k_EImageFormat_RGBA16161616,
+       k_EImageFormat_UVLX8888
+} EImageFormat_t;
+
+const char *vtf_format_strings[] = 
+{
+       // Name                                                                 // Supported?
+       "RGBA8888",
+       "ABGR8888",
+       "RGB888",
+       "BGR888",
+       "RGB565",
+       "I8",
+       "IA88",
+       "P8",
+       "A8",
+       "RGB888_BLUESCREEN",
+       "BGR888_BLUESCREEN",
+       "ARGB8888",
+       "BGRA8888",
+#ifdef USE_LIBRGBCX
+       "DXT1 (rgbcx.h)",
+#else
+       "DXT1 (stb_dxt.h)",
+#endif
+       "DXT3",
+#ifdef USE_LIBRGBCX
+       "DXT5 (rgbcx.h)",
+#else
+       "DXT5 (stb_dxt.h)",
+#endif
+       "BGRX8888",
+       "BGR565",
+       "BGRX5551",
+       "BGRA4444",
+       "DXT1_ONEBITALPHA",
+       "BGRA5551",
+       "UV88",
+       "UVWQ8888",
+       "RGBA16161616F",
+       "RGBA16161616",
+       "UVLX8888"
+};
+
+enum flag
+{
+       // Flags from the *.txt config file
+       TEXTUREFLAGS_POINTSAMPLE = 0x00000001,
+       TEXTUREFLAGS_TRILINEAR = 0x00000002,
+       TEXTUREFLAGS_CLAMPS = 0x00000004,
+       TEXTUREFLAGS_CLAMPT = 0x00000008,
+       TEXTUREFLAGS_ANISOTROPIC = 0x00000010,
+       TEXTUREFLAGS_HINT_DXT5 = 0x00000020,
+       TEXTUREFLAGS_PWL_CORRECTED = 0x00000040,
+       TEXTUREFLAGS_NORMAL = 0x00000080,
+       TEXTUREFLAGS_NOMIP = 0x00000100,
+       TEXTUREFLAGS_NOLOD = 0x00000200,
+       TEXTUREFLAGS_ALL_MIPS = 0x00000400,
+       TEXTUREFLAGS_PROCEDURAL = 0x00000800,
+
+       // These are automatically generated by vtex from the texture data.
+       TEXTUREFLAGS_ONEBITALPHA = 0x00001000,
+       TEXTUREFLAGS_EIGHTBITALPHA = 0x00002000,
+
+       // Newer flags from the *.txt config file
+       TEXTUREFLAGS_ENVMAP = 0x00004000,
+       TEXTUREFLAGS_RENDERTARGET = 0x00008000,
+       TEXTUREFLAGS_DEPTHRENDERTARGET = 0x00010000,
+       TEXTUREFLAGS_NODEBUGOVERRIDE = 0x00020000,
+       TEXTUREFLAGS_SINGLECOPY = 0x00040000,
+       TEXTUREFLAGS_PRE_SRGB = 0x00080000,
+
+       TEXTUREFLAGS_UNUSED_00100000 = 0x00100000,
+       TEXTUREFLAGS_UNUSED_00200000 = 0x00200000,
+       TEXTUREFLAGS_UNUSED_00400000 = 0x00400000,
+
+       TEXTUREFLAGS_NODEPTHBUFFER = 0x00800000,
+
+       TEXTUREFLAGS_UNUSED_01000000 = 0x01000000,
+
+       TEXTUREFLAGS_CLAMPU = 0x02000000,
+       TEXTUREFLAGS_VERTEXTEXTURE = 0x04000000,
+       TEXTUREFLAGS_SSBUMP = 0x08000000,
+
+       TEXTUREFLAGS_UNUSED_10000000 = 0x10000000,
+
+       TEXTUREFLAGS_BORDER = 0x20000000,
+
+       TEXTUREFLAGS_UNUSED_40000000 = 0x40000000,
+       TEXTUREFLAGS_UNUSED_80000000 = 0x80000000,
+};
+
+typedef struct vtfheader
+{
+       union
+       {
+       char                            signature[4];                   // File signature ("VTF\0"). (or as little-endian integer, 0x00465456)
+       uint32_t                        usig;
+       };
+       
+       unsigned int    version[2];                             // version[0].version[1] (currently 7.2).
+       unsigned int    headerSize;                             // Size of the header struct  (16 byte aligned; currently 80 bytes) + size of the resources dictionary (7.3+).
+       unsigned short  width;                                  // Width of the largest mipmap in pixels. Must be a power of 2.
+       unsigned short  height;                                 // Height of the largest mipmap in pixels. Must be a power of 2.
+       unsigned int    flags;                                  // VTF flags.
+       unsigned short  frames;                                 // Number of frames, if animated (1 for no animation).
+       unsigned short  firstFrame;                             // First frame in animation (0 based).
+       unsigned char   padding0[4];                    // reflectivity padding (16 byte alignment).
+       float                           reflectivity[3];                // reflectivity vector.
+       unsigned char   padding1[4];                    // reflectivity padding (8 byte packing).
+       float                           bumpmapScale;                   // Bumpmap scale.
+       unsigned int    highResImageFormat;     // High resolution image format.
+       unsigned char   mipmapCount;                    // Number of mipmaps.
+       unsigned int    lowResImageFormat;      // Low resolution image format (always DXT1).
+       unsigned char   lowResImageWidth;               // Low resolution image width.
+       unsigned char   lowResImageHeight;      // Low resolution image height.
+
+       // 7.2+
+       unsigned short  depth;                                  // Depth of the largest mipmap in pixels.
+                                                                                                       // Must be a power of 2. Can be 0 or 1 for a 2D texture (v7.2 only).
+
+       // 7.3+
+       unsigned char   padding2[3];                    // depth padding (4 byte alignment).
+       unsigned int    numResources;                   // Number of resources this vtf has
+       
+       unsigned char  padding3[8];        // Necessary on certain compilers
+} vtfheader_t;
+
+#pragma pack(pop)
+
+typedef struct mipimg
+{
+       uint32_t w;
+       uint32_t h;
+       uint32_t src_offset;
+} mipimg_t;
+
+int nbvtf_power2( uint32_t x )
+{
+    return (x != 0) && ((x & (x - 1)) == 0);
+}
+
+int nbvtf_power2x( uint32_t y, uint32_t x )
+{
+       return nbvtf_power2( y ) && nbvtf_power2( x );
+}
+
+int nbvtf_lower( int *x, int *y )
+{
+       if( *x == 1 && *y == 1 )
+       {
+               return 0;
+       }
+
+       *x = nbvtf__max( 1, (*x)/2 );
+       *y = nbvtf__max( 1, (*y)/2 );
+       
+       return 1;
+}
+
+int nbvtf_lowres_index( int w, int h )
+{
+       int x, y;
+       int i = 0;
+       
+       x = w;
+       y = h;
+       
+       while(1)
+       {
+               if( (x <= 16) && ( y <= 16 ) )
+               {
+                       return i;
+               }
+               
+               i ++;
+               
+               nbvtf_lower( &x, &y );
+       }
+}
+
+// Simple box filter downscale
+void nbvtf_downscale( uint8_t *src, int w, int h, int dw, int dh, uint8_t *dest )
+{
+   int bx = w/dw;
+   int by = h/dh;
+   int div = bx*by;
+
+       for( int y = 0; y < dh; y ++ )
+   for( int x = 0; x < dw; x ++ )
+   {
+      // Average block colours
+      uint32_t tr = 0, tg = 0, tb = 0, ta = 0;
+
+      for( int yy = 0; yy < by; yy ++ )
+      for( int xx = 0; xx < bx; xx ++ )
+      {
+         uint8_t *psrc = &src[ (x*bx+xx + (y*by+yy)*w)*4 ];
+         tr+=psrc[0];
+         tg+=psrc[1];
+         tb+=psrc[2];
+         ta+=psrc[3];
+      }
+      
+      uint8_t *pdst = &dest[ (y*dw + x)*4 ];
+      pdst[0] = tr / div;
+      pdst[1] = tg / div;
+      pdst[2] = tb / div;
+      pdst[3] = ta / div;
+       }
+}
+
+uint8_t *nbvtf_create_mipmaps( uint8_t *src, int w, int h, mipimg_t *offsets, int *num )
+{
+       int memory = 0;
+       int x, y, i;
+       uint32_t offset;
+       
+       x = w;
+       y = h;
+       while( nbvtf_lower( &x, &y ) )
+               memory += x*y*4;
+       
+       uint8_t *mipmem = (uint8_t *)malloc( memory );
+       
+       if( mipmem )
+       {
+               x = w;
+               y = h;
+               i = 0;
+               offset = 0;
+               
+               uint8_t *dest = mipmem;
+               
+               while(1)
+               {
+                       if( !nbvtf_lower( &x, &y ) )
+                               break;
+
+                       nbvtf_downscale( src, w, h, x, y, dest );
+                       
+                       offsets[ i ].src_offset = offset;
+                       offsets[ i ].w = x;
+                       offsets[ i ].h = y;
+                       i ++;
+                       
+                       offset += x*y*4;
+                       dest = mipmem + offset;
+               }
+               
+               *num = i;
+               return mipmem;
+       }
+       else
+       {
+               NBVTF_ERR( "nbvtf_write:err out of memory allocating mipmap buffer!\n" );
+               return NULL;
+       }
+}
+
+void nbvtf_reflectivity( uint8_t *src, int w, int h, float *dest )
+{
+       uint32_t totals[3] = {0,0,0};
+       
+       for( int i = 0; i < w*h; i ++ )
+       {
+               totals[0] += src[i*4+0];
+               totals[1] += src[i*4+1];
+               totals[2] += src[i*4+2];
+       }
+       
+       dest[0] = (float)( totals[0] / (w*h) ) / 255.0f;
+       dest[1] = (float)( totals[1] / (w*h) ) / 255.0f;
+       dest[2] = (float)( totals[2] / (w*h) ) / 255.0f;
+}
+
+#ifdef NBVTF_ALLOW_EXPORT
+void nbvtf_debug_view_mips( uint8_t *src, int w, int h )
+{
+       int x, y, i;
+       char fnbuf[512];
+       
+       x = w;
+       y = h;
+       i = 1;
+
+       uint8_t *dest = src;
+
+       while( nbvtf_lower( &x, &y ) )
+       {
+               sprintf( fnbuf, "mip_%d.png", i ++ );
+       
+               stbi_write_png( fnbuf, x,y, 4, dest, x*4 );
+               dest += x*y*4;
+       }
+}
+#endif
+
+void nbvtf_dxt_pad( uint8_t *src, int bx, int by, int w, int h, uint8_t *dest )
+{
+       int px = bx*4;
+       int py = by*4;
+       
+       uint32_t *stream = (uint32_t *)src;
+       uint32_t *stream_out = (uint32_t *)dest;
+
+       for( int y = 0; y < 4; y ++ )
+       {
+               for( int x = 0; x < 4; x ++ )
+               {
+                       stream_out[ y*4+x ] = stream[ nbvtf__min( py+y, h-1 )*w + nbvtf__min( px+x, w-1 ) ];
+               }
+       }
+}
+
+uint32_t nbvtf_dxt_sizeimg( int w, int h, int alpha )
+{
+       uint32_t blocks_x, blocks_y;
+       int block_size = alpha? 16: 8;
+
+       blocks_x = ((uint32_t)w) >> 2;
+       blocks_y = ((uint32_t)h) >> 2;
+       
+       int padx = w % 4 != 0? 1: 0;
+       int pady = h % 4 != 0? 1: 0;
+       
+       return (blocks_x+padx)*(blocks_y+pady)*block_size;
+}
+
+uint32_t nbvtf_sizeimg( int w, int h, EImageFormat_t format )
+{
+       if( format == k_EImageFormat_DXT5 || format == k_EImageFormat_DXT1 )
+       {
+               return nbvtf_dxt_sizeimg( w, h, format == k_EImageFormat_DXT1? 0: 1 );
+       }
+       
+       if( format == k_EImageFormat_BGR888 )
+               return w*h*3;
+               
+       if( format == k_EImageFormat_ABGR8888 )
+               return w*h*4;
+               
+       return 0;
+}
+
+void nbvtf_dxt_block( uint8_t *dest, uint8_t *src, int alpha, int qual )
+{
+#ifdef USE_LIBRGBCX
+       // TODO: move this somewehre else
+       static int init = 0;
+       if( !init )
+       {
+               rgbcx__init();
+               init = 1;
+       }
+
+       if( alpha )
+       {
+               rgbcx__encode_bc3( qual, dest, src );
+       }
+       else
+       {
+               rgbcx__encode_bc1( qual, dest, src, 0, 0 );
+       }
+#else
+       stb_compress_dxt_block( dest, src, alpha, STB_DXT_HIGHQUAL );
+#endif
+}
+
+void nbvtf_compress_dxt( uint8_t *src, int w, int h, int alpha, int qual,
+      uint8_t *dest )
+{
+       uint32_t blocks_x, blocks_y;
+
+       blocks_x = ((uint32_t)w) >> 2;
+       blocks_y = ((uint32_t)h) >> 2;
+       
+       int padx = w % 4 != 0? 1: 0;
+       int pady = h % 4 != 0? 1: 0;
+       
+       int block_size = alpha? 16: 8;
+       
+       uint8_t *dest_block = dest;
+       
+       uint8_t working_block[ 4*4*4 ];
+       
+       // Compress rows
+       for( int y = 0; y < blocks_y; y ++ )
+       {
+               for( int x = 0; x < blocks_x; x ++ )
+               {
+                       uint8_t *src_begin = src + (y*w*4 + x*4)*4;
+                       for( int i = 0; i < 4; i ++ )
+                       {
+                               memcpy( working_block + i*4*4, src_begin + w*4*i, 4*4 );
+                       }
+                       
+                       nbvtf_dxt_block( dest_block, working_block, alpha, qual );
+                       dest_block += block_size;
+               }
+               
+               if( padx )
+               {
+                       nbvtf_dxt_pad( src, blocks_x, y, w, h, working_block );
+                       nbvtf_dxt_block( dest_block, working_block, alpha, qual );
+                       dest_block += block_size;
+               }
+       }
+       
+       // Compress remainder row
+       if( pady )
+       {
+               for( int x = 0; x < blocks_x; x ++ )
+               {
+                       nbvtf_dxt_pad( src, x, blocks_y, w, h, working_block );
+                       nbvtf_dxt_block( dest_block, working_block, alpha, qual );
+                       dest_block += block_size;
+               }
+       }
+       
+       // Compress last little corner
+       if( padx && pady )
+       {
+               nbvtf_dxt_pad( src, blocks_x, blocks_y, w, h, working_block );
+               nbvtf_dxt_block( dest_block, working_block, alpha, qual );
+       }
+}
+
+void nbvtf_swizzle_to( uint8_t *src, int n, int nc, uint8_t *dest )
+{
+       for( int i = 0; i < n; i ++ )
+       {
+               for( int j = 0; j < nc; j ++ )
+               {
+                       dest[ i*nc+nc-j-1 ] = src[ i*4+j ];
+               }
+       }
+}
+
+void nbvtf_write_img_data( uint8_t *src, int w, int h, 
+      EImageFormat_t format, int qual, uint8_t *wb, FILE *file )
+{
+       switch( format )
+       {
+               case k_EImageFormat_DXT1:
+                       nbvtf_compress_dxt( src, w, h, 0, qual, wb );
+                       fwrite( wb, nbvtf_dxt_sizeimg( w, h, 0 ), 1, file );
+               break;
+               case k_EImageFormat_DXT5:
+                       nbvtf_compress_dxt( src, w, h, 1, qual, wb );
+                       fwrite( wb, nbvtf_dxt_sizeimg( w, h, 1 ), 1, file );
+               break;
+               case k_EImageFormat_ABGR8888:
+                       nbvtf_swizzle_to( src, w*h, 4, wb );
+                       fwrite( wb, w*h*4, 1, file );
+               break;
+               case k_EImageFormat_BGR888:
+                       nbvtf_swizzle_to( src, w*h, 3, wb );
+                       fwrite( wb, w*h*3, 1, file );
+               break;
+               
+               default:
+               break;
+       }
+}
+
+#ifdef NBVTF_AS_SO
+__attribute__((visibility("default")))
+#endif
+int nbvtf_write( uint8_t *reference, int w, int h, int mipmap, 
+      EImageFormat_t format, int qual, uint32_t usr_flags, const char *dest )
+{
+       if( !nbvtf_power2x(w,h) )
+       {
+               NBVTF_ERR( "nbvtf_write:err image dimentions were not power of two (%d %d)\n", w, h );
+               return 0;
+       }
+
+       mipimg_t mip_offsets[ 16 ];
+       int num_mips;
+       
+   uint8_t *src;
+
+   // Convert to directx normal
+   if( usr_flags & TEXTUREFLAGS_NORMAL )
+   {
+      src = malloc( w*h*4 );
+      for( int i = 0; i < w*h; i ++ )
+      {
+         src[i*4+0] = reference[i*4+0];
+         src[i*4+1] = 0xFF-reference[i*4+1];
+         src[i*4+2] = reference[i*4+2];
+         src[i*4+3] = reference[i*4+3];
+      }
+   }
+   else
+      src = reference;
+       
+       uint8_t *mip_data = nbvtf_create_mipmaps( src, w, h, mip_offsets, &num_mips );
+       
+       if( !mip_data )
+       {
+               NBVTF_ERR( "nbvtf_write:err mipmap data failed to generate" );
+
+      if( usr_flags & TEXTUREFLAGS_NORMAL )
+         free( src );
+
+               return 0;
+       }
+       
+       vtfheader_t header = {0};
+       
+       header.usig = 0x00465456;
+       header.headerSize = sizeof( vtfheader_t );
+       header.version[0] = 7;
+       header.version[1] = 2;
+       
+       header.width = w;
+       header.height = h;
+       header.flags = usr_flags;
+       
+       // Append format flags
+       if( !mipmap )
+       {
+               header.flags |= TEXTUREFLAGS_NOLOD;
+               header.flags |= TEXTUREFLAGS_NOMIP;
+       }
+       
+       if( format == k_EImageFormat_DXT5 || format == k_EImageFormat_ABGR8888 )
+       {
+               header.flags |= TEXTUREFLAGS_EIGHTBITALPHA;
+       }
+       
+       header.frames = 1;
+       header.firstFrame = 0;
+       nbvtf_reflectivity( mip_data + mip_offsets[ num_mips-1 ].src_offset, 1,1, header.reflectivity );
+       header.bumpmapScale = 1.0f;
+       
+       header.highResImageFormat = format;
+       header.mipmapCount = mipmap? 
+      nbvtf__min(num_mips,NBVTF_MAX_MIPLEVELS)+1: 1;
+       
+       header.lowResImageFormat = k_EImageFormat_DXT1;
+
+       header.depth = 1;
+       header.numResources = 0;
+       
+       int lr_index = nbvtf_lowres_index( w, h );
+       
+       uint8_t *lr_src;
+       
+       if( lr_index )
+       {
+               mipimg_t *mip = mip_offsets + (lr_index-1);
+               lr_src = mip_data + mip->src_offset;
+               
+               header.lowResImageWidth = mip->w;
+               header.lowResImageHeight = mip->h;
+       }
+       else
+       {
+               lr_src = src;
+               
+               header.lowResImageWidth = w;
+               header.lowResImageHeight = h;
+       }
+       
+       uint32_t size_highres = nbvtf_sizeimg( w, h, format );
+       uint32_t size_lowres = 
+      nbvtf_dxt_sizeimg( header.lowResImageWidth, header.lowResImageHeight, 0 );
+       
+       uint8_t *working_buffer = 
+      (uint8_t *)malloc( nbvtf__max( size_highres, size_lowres ) );
+       
+       if( !working_buffer )
+       {
+               NBVTF_ERR( "nbvtf_write:err out of memory allocating working buffer\n" );
+               free( mip_data );
+
+      if( usr_flags & TEXTUREFLAGS_NORMAL )
+         free( src );
+
+               return 0;
+       }
+       
+       FILE *file = fopen( dest, "wb" );
+       
+       if( !file )
+       {
+               NBVTF_ERR( "nbvtf_write:err could not open file stream for writing\n" );
+               
+               free( working_buffer );
+               free( mip_data );
+               
+      if( usr_flags & TEXTUREFLAGS_NORMAL )
+         free( src );
+
+               return 0;
+       }
+       
+       // Write header
+       fwrite( &header, sizeof( vtfheader_t ), 1, file );
+       
+       // Write low res
+       nbvtf_write_img_data( 
+               lr_src, header.lowResImageWidth, header.lowResImageHeight, 
+      k_EImageFormat_DXT1, qual, working_buffer, file 
+       );
+       
+       // Write texture data
+       if( mipmap )
+       {
+      // !! Experimental !!
+      int start = nbvtf__max( 0, num_mips-NBVTF_MAX_MIPLEVELS );
+      
+               for( int i = start; i < num_mips; i ++ )
+               {
+                       mipimg_t *mip = mip_offsets + (num_mips - i -1);
+                       nbvtf_write_img_data( mip_data + mip->src_offset, mip->w, mip->h, 
+               format, qual, working_buffer, file );
+               }
+       }
+       
+       // Write high resolution
+       nbvtf_write_img_data( src, w, h, format, qual, working_buffer, file );
+       
+       fclose( file );
+
+       free( working_buffer );
+       free( mip_data );
+
+   if( usr_flags & TEXTUREFLAGS_NORMAL )
+      free( src );
+       
+       return 1;
+}
+
+#ifdef NBVTF_AS_SO
+__attribute__((visibility("default")))
+#endif
+int nbvtf_convert( const char *src, int w, int h, int mipmap, 
+      EImageFormat_t format, int qual, uint32_t usr_flags, const char *dest )
+{
+       if( (w && h) && !nbvtf_power2x(w,h) )
+       {
+               NBVTF_ERR( "nbvtf_convert:err requested dimentions were not power of two (%d %d)\n", w, h );
+               return 0;
+       }
+       
+       int x,y,n;
+       uint8_t *data = stbi_load( src, &x, &y, &n, 4 );
+       
+       if( data )
+       {
+               if( !nbvtf_power2x(x,y) )
+               {
+                       NBVTF_ERR( "nbvtf_convert:err loaded image dimentions were not power two (%d %d)\n", x, y );
+                       stbi_image_free( data );
+                       return 0;
+               }
+               
+               // Image size needs retargeting
+               if( (w && h) && ( x > w || y > h ) )
+         nbvtf_downscale( data, x, y, w, h, data );
+               
+               int status = nbvtf_write( data, w, h, mipmap, format, qual, 
+            usr_flags, dest );
+
+               stbi_image_free( data );
+               
+               return status;
+       }
+       else
+       {
+               return 0;
+       }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/*
+LICENSE - Applies to nbvtf.h, pynbvtf.py, librgbcx.cc, librgbcx.h, vtf_cmd.c
+------------------------------------------------------------------------------
+Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/