2025-02-11 02:51:24 -06:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <math.h>
# include <MagickWand/MagickWand.h>
2025-02-18 15:11:19 -06:00
# ifdef _MSC_VER
# include <Windows.h>
# endif
2025-02-11 02:51:24 -06:00
int isPowerOfTwo ( int x ) { return x & & ( ! ( x & ( x - 1 ) ) ) ; }
unsigned int next_power_of_2 ( unsigned int n ) {
if ( n = = 0 ) {
return 1 ;
}
unsigned int p = 1 ;
while ( p < n ) {
p < < = 1 ;
}
return p ;
}
unsigned int nearest_power_of_2 ( unsigned int n ) {
if ( n = = 0 ) {
return 1 ;
}
unsigned int next_power = next_power_of_2 ( n ) ;
unsigned int prev_power = next_power > > 1 ;
if ( n - prev_power < = next_power - n ) {
return prev_power ;
} else {
return next_power ;
}
}
struct options {
2025-02-12 14:57:36 -06:00
uint32_t r ;
2025-02-11 02:51:24 -06:00
int s ;
2025-02-19 11:46:48 -06:00
uint16_t c ;
2025-02-11 02:51:24 -06:00
int o ;
int l ;
int w ;
int h ;
2025-02-18 01:20:45 -06:00
int d ;
2025-02-11 02:51:24 -06:00
} ;
void printHelp ( ) {
printf (
2025-02-11 23:52:45 -06:00
" usage: kaveat <target MOV> <source images> [options] \n "
2025-02-11 02:51:24 -06:00
" Where each source begins with +, and is added as an animation frame. \n "
" A maximum of 360 different frames can be used for a single avatar. \n "
" If the size of any frame does not match, it will be resized to match the first frame. \n "
" \n "
2025-02-18 01:20:45 -06:00
" When using KAvEAT to create environment textures, please keep in mind that Worlds has \n "
" a limit of 9x9 tiles per CMP/MOV file due to the URL parser only reading single digits. \n "
" This makes the practical maximum resolution of avatars 2048x2048. World textures can not \n "
" have more than 9 frames but can bypass both limits by splitting walls into more pieces. \n "
" \n "
2025-02-11 02:51:24 -06:00
" Available options: \n "
" -r # Maximum resolution, where # is a power of 2. For example, 256 pixels would be \" -r 8 \" \n "
" -s Stretch the image when non-pot or mismatched size, as opposed to fitting it within. \n "
" -c # Defines the number of colors in the palette, counting from 1. Maximum 256. \n "
" -o Opaque mode. Do not make the first color in the image palette transparent. \n "
" The background of letterboxed images is internally set to the first palette color, \n "
" so using this setting with different image sizes will cause visible bars to appear \n "
" unless you combine it with the -s option, which will always fill the canvas for each \n "
" frame. The same is true for all imported frames which contain an alpha channel. \n "
2025-02-18 01:20:45 -06:00
" -d Don't run COMPIMG, don't delete temporary files. Useful if COMPIMG is broken for some \n "
" reason, or if you want to customize the command line options given to it. \n "
2025-02-11 02:51:24 -06:00
// " -l Enable Lanczos filtering when scaling the image.\n" //point filter unimplemented
//" -w Specify the number of subdivisions to make horizontally per frame.\n"
//" Use this if you are importing a spritesheet. Each cut is perfectly even.\n"
//" Note that this process happens AFTER importing each image, and will be\n"
//" applied to each one separately. DO NOT use this to combine a spritesheet"
//" with individual frames or you will have a very, very bad time."
2025-02-11 23:52:45 -06:00
//" -h Same as -w, but vertical. If used with -w, KAvEAT will create each chunk\n"
2025-02-11 02:51:24 -06:00
//" from each row left-to-right first, not each column top-to-bottom.\n"
) ;
2025-02-18 15:11:19 -06:00
# ifdef _MSC_VER
DWORD procIDs [ 2 ] ;
DWORD maxCount = 2 ;
if ( GetConsoleProcessList ( ( LPDWORD ) procIDs , maxCount ) = = 1 ) {
printf ( " \n \n THIS PROGRAM IS DESIGNED FOR USE WITH A TERMINAL. \n "
" Please run cmd.exe first! Then you can use the tool. \n " ) ;
system ( " pause " ) ;
}
# endif
2025-02-11 02:51:24 -06:00
}
2025-02-12 14:57:36 -06:00
void printNoFrames ( ) { printf ( " You need to specify at least one frame to use, goober. \n " ) ; }
2025-02-11 02:51:24 -06:00
int main ( const int argc , char * argv [ ] ) {
printf (
2025-02-19 11:46:48 -06:00
" Knowledge Adventure Worlds Easy Avatar Tool, version 1.1.1 \n "
2025-02-11 02:51:24 -06:00
" Copyright (c) 2025 Brett \" bonkmaykr \" Bergstrom \n "
" This program is free software under the MIT license. \n "
" \n "
) ;
if ( argc < 2 ) { printHelp ( ) ; return 0 ; }
2025-02-12 14:57:36 -06:00
// Strings allocated with variable size according to filename need to be done on the heap due to an MSVC bug.
2025-02-11 23:52:45 -06:00
char * filename = ( char * ) malloc ( strlen ( argv [ 1 ] ) * sizeof ( char ) ) ; // MSVC
strcpy ( filename , argv [ 1 ] ) ;
2025-02-11 02:51:24 -06:00
printf ( " Creating avatar %s.mov \n " , filename ) ;
if ( argc < 3 ) { printNoFrames ( ) ; return 0 ; }
MagickWand * mw = NULL ;
MagickWandGenesis ( ) ;
mw = NewMagickWand ( ) ;
struct options opt ;
opt . r = 0 ;
opt . s = 0 ;
2025-02-19 11:46:48 -06:00
opt . c = 0xDEAD ;
2025-02-11 02:51:24 -06:00
opt . o = 0 ;
opt . l = 0 ;
opt . w = 0 ;
opt . h = 0 ;
2025-02-18 01:20:45 -06:00
opt . d = 0 ;
2025-02-11 02:51:24 -06:00
uint16_t numFrames = 0 ;
char * frames [ 360 ] ;
int i = 2 ;
while ( i < argc ) {
if ( argv [ i ] [ 0 ] = = ' + ' ) {
2025-02-18 01:20:45 -06:00
if ( numFrames < 360 ) {
frames [ numFrames ] = ( char * ) malloc ( strlen ( argv [ i ] ) + 1 ) ;
memcpy ( frames [ numFrames ] , & argv [ i ] [ 1 ] , strlen ( argv [ i ] ) + 1 ) ;
printf ( " Adding frame to search index: %s \n " , frames [ numFrames ] ) ;
2025-02-11 02:51:24 -06:00
2025-02-18 01:20:45 -06:00
numFrames + + ;
} else printf ( " REJECTING new frame as it exceeds the frame limit. \n " ) ;
2025-02-12 14:57:36 -06:00
} else if ( strcmp ( argv [ i ] , " -r " ) = = 0 ) {
if ( i = = argc - 1 ) { printf ( " No parameter for option: -r \n " ) ; return 0 ; }
int r = pow ( 2 , trunc ( atof ( argv [ i + 1 ] ) ) ) ;
if ( r > 4096 ) { printf ( " I really, really hope your image is not %d pixels in size. \n " , r ) ; }
else if ( r < 2 ) { printf ( " I'm sorry, Dave, I'm afraid I can't do that. (%d pixel(s) is too small, clamping to 2.) \n " , r ) ; r = 2 ; }
printf ( " Limiting resolution per frame to %dx%d pixels. \n " , r , r ) ;
opt . r = r ;
i + + ;
2025-02-11 02:51:24 -06:00
} else if ( strcmp ( argv [ i ] , " -s " ) = = 0 ) {
2025-02-12 14:57:36 -06:00
opt . s = ! opt . s ;
} else if ( strcmp ( argv [ i ] , " -o " ) = = 0 ) {
opt . o = ! opt . o ;
} else if ( strcmp ( argv [ i ] , " -c " ) = = 0 ) {
if ( i = = argc - 1 ) { printf ( " No parameter for option: -c \n " ) ; return 0 ; }
const int c = trunc ( atof ( argv [ i + 1 ] ) ) ;
2025-02-19 11:46:48 -06:00
if ( c < 1 | | c > 256 - ( ! opt . o ) ) { printf ( " Invalid number of colors: %s \n The maximum palette size is 255 for transparent MOVs and 256 for opaque ones. \n " , argv [ i + 1 ] ) ; return 0 ; }
2025-02-12 14:57:36 -06:00
opt . c = c - 1 ;
i + + ;
2025-02-18 01:20:45 -06:00
} else if ( strcmp ( argv [ i ] , " -d " ) = = 0 ) {
opt . d = ! opt . d ;
2025-02-11 02:51:24 -06:00
} else {
printf ( " I don't know what \" %s \" means! \n " , argv [ i ] ) ;
return 0 ;
}
i + + ;
}
2025-02-12 14:57:36 -06:00
if ( numFrames = = 0 ) { printNoFrames ( ) ; return 0 ; }
2025-02-19 11:46:48 -06:00
if ( opt . c = = 0xDEAD ) { if ( opt . o ) opt . c = 254 ; else opt . c = 255 ; }
2025-02-12 14:57:36 -06:00
2025-02-11 02:51:24 -06:00
if ( MagickReadImage ( mw , frames [ 0 ] ) = = MagickFalse ) { printf ( " ERROR trying to read %s \n " , frames [ 0 ] ) ; return 0 ; }
MagickSetImageFormat ( mw , " BMP3 " ) ;
MagickSetImageDepth ( mw , 8 ) ;
MagickSetImageType ( mw , PaletteType ) ;
MagickSetImageColorspace ( mw , UndefinedColorspace ) ;
uint16_t width = MagickGetImageWidth ( mw ) ;
uint16_t height = MagickGetImageHeight ( mw ) ;
2025-02-12 14:57:36 -06:00
// -r option
if ( opt . r > 0 ) {
if ( opt . s > 0 ) {
printf ( " Squishing to resolution limit... \n " ) ;
if ( width > opt . r ) {
MagickResizeImage ( mw , nearest_power_of_2 ( width ) , nearest_power_of_2 ( height ) , LanczosFilter ) ;
width = opt . r ;
}
if ( height > opt . r ) {
MagickResizeImage ( mw , nearest_power_of_2 ( width ) , nearest_power_of_2 ( height ) , LanczosFilter ) ;
height = opt . r ;
}
}
else {
printf ( " Fitting to resolution limit... \n " ) ;
if ( width > opt . r | | height > opt . r ) {
if ( width > height ) {
const double ratio = ( double ) height / ( double ) width ;
printf ( " Letterboxing image to %ux%u. \n " , opt . r , opt . r * ratio ) ;
PixelWand * pw = NewPixelWand ( ) ;
MagickGetImagePixelColor ( mw , 0 , 0 , pw ) ;
MagickSetImageBackgroundColor ( mw , pw ) ;
if ( ratio < 1 ) MagickResizeImage ( mw , opt . r , opt . r * ratio , LanczosFilter ) ;
//MagickExtentImage(mw, opt.r, opt.r, 0, (opt.r-(opt.r*ratio))*-0.5);
width = opt . r ;
height = opt . r ;
} else if ( height > width ) {
const double ratio = ( double ) width / ( double ) height ;
printf ( " Pillarboxing image to %ux%u. \n " , opt . r * ratio , opt . r ) ;
PixelWand * pw = NewPixelWand ( ) ;
MagickGetImagePixelColor ( mw , 0 , 0 , pw ) ;
MagickSetImageBackgroundColor ( mw , pw ) ;
if ( ratio < 1 ) MagickResizeImage ( mw , opt . r * ratio , opt . r , LanczosFilter ) ;
//MagickExtentImage(mw, opt.r, opt.r, (opt.r-(opt.r*ratio))*-0.5, 0);
width = opt . r ;
height = opt . r ;
} else /*if (height == width)*/ {
printf ( " Resizing image to %ux%u. \n " , opt . r , opt . r ) ;
MagickResizeImage ( mw , opt . r , opt . r , LanczosFilter ) ;
width = opt . r ;
height = opt . r ;
}
}
}
}
2025-02-11 02:51:24 -06:00
if ( ! isPowerOfTwo ( width ) | | ! isPowerOfTwo ( height ) ) printf ( " Uh-oh! Image size %hux%hu is not a power of two! \n " , width , height ) ;
if ( opt . s > 0 | | ( ! isPowerOfTwo ( width ) & & ! isPowerOfTwo ( height ) ) ) { // both are bad!
printf ( " Resizing image to %ux%u. \n " , nearest_power_of_2 ( width ) , nearest_power_of_2 ( height ) ) ;
MagickResizeImage ( mw , nearest_power_of_2 ( width ) , nearest_power_of_2 ( height ) , LanczosFilter ) ;
}
else if ( ! isPowerOfTwo ( width ) & & isPowerOfTwo ( height ) ) { // just the width is bad
printf ( " Letterboxing image to %ux%u. \n " , height , height ) ;
PixelWand * pw = NewPixelWand ( ) ;
MagickGetImagePixelColor ( mw , 0 , 0 , pw ) ;
MagickSetImageBackgroundColor ( mw , pw ) ;
const double ratio = ( double ) height / ( double ) width ;
if ( ratio < 1 ) MagickResizeImage ( mw , height , height * ratio , LanczosFilter ) ;
MagickExtentImage ( mw , height , height , 0 , ( height - ( height * ratio ) ) * - 0.5 ) ;
} else if ( ! isPowerOfTwo ( height ) & & isPowerOfTwo ( width ) ) { // just the height is bad
printf ( " Pillarboxing image to %ux%u. \n " , width , width ) ;
PixelWand * pw = NewPixelWand ( ) ;
MagickGetImagePixelColor ( mw , 0 , 0 , pw ) ;
MagickSetImageBackgroundColor ( mw , pw ) ;
const double ratio = ( double ) width / ( double ) height ;
if ( ratio < 1 ) MagickResizeImage ( mw , width * ratio , width , LanczosFilter ) ;
MagickExtentImage ( mw , width , width , ( width - ( width * ratio ) ) * - 0.5 , 0 ) ;
}
width = MagickGetImageWidth ( mw ) ;
height = MagickGetImageHeight ( mw ) ;
double ratio1 = ( double ) width / ( double ) height ;
double ratio2 ;
PixelWand * pw = NewPixelWand ( ) ;
MagickGetImagePixelColor ( mw , 0 , 0 , pw ) ;
2025-02-12 14:57:36 -06:00
printf ( " Importing %d frames... \n " , numFrames ) ;
2025-02-11 02:51:24 -06:00
i = 1 ;
while ( i < numFrames ) {
// Letterbox background default to transparent pixel
MagickSetImageBackgroundColor ( mw , pw ) ;
// Load the image
if ( MagickReadImage ( mw , frames [ i ] ) = = MagickFalse ) { printf ( " ERROR trying to read %s \n " , frames [ i ] ) ; return 0 ; }
MagickSetImageFormat ( mw , " BMP3 " ) ;
MagickSetImageDepth ( mw , 8 ) ;
MagickSetImageType ( mw , PaletteType ) ;
MagickSetImageColorspace ( mw , UndefinedColorspace ) ;
// Resize the image to fit the first frame
ratio2 = ( double ) MagickGetImageWidth ( mw ) / ( double ) MagickGetImageHeight ( mw ) ;
MagickSetImageGravity ( mw , CenterGravity ) ;
if ( MagickGetImageHeight ( mw ) ! = height | | MagickGetImageWidth ( mw ) ! = width ) {
if ( opt . s = = 0 ) {
if ( ratio1 > ratio2 ) {
double scale = ( double ) height / ( double ) MagickGetImageHeight ( mw ) ;
MagickResizeImage ( mw , MagickGetImageWidth ( mw ) * scale , height , LanczosFilter ) ;
} else {
double scale = ( double ) width / ( double ) MagickGetImageWidth ( mw ) ;
MagickResizeImage ( mw , width , MagickGetImageHeight ( mw ) * scale , LanczosFilter ) ;
}
} else {
if ( MagickGetImageWidth ( mw ) ! = width | | MagickGetImageHeight ( mw ) ! = height ) {
double scalew = ( double ) width / ( double ) MagickGetImageWidth ( mw ) ;
double scaleh = ( double ) height / ( double ) MagickGetImageHeight ( mw ) ;
MagickResizeImage ( mw , MagickGetImageWidth ( mw ) * scalew , MagickGetImageHeight ( mw ) * scaleh , LanczosFilter ) ;
}
}
}
i + + ;
}
// Create spritesheet from frames
MagickResetIterator ( mw ) ;
mw = MagickAppendImages ( mw , MagickFalse ) ;
int piecesX = width / 256 ; if ( width < 256 ) piecesX = 1 ;
int piecesY = height / 256 ; if ( height < 256 ) piecesY = 1 ;
printf ( " Cutting up frames into %dx%d chunks... \n " , width / piecesX , height / piecesY ) ;
i = 0 ;
remove ( " segments.txt " ) ;
FILE * partlist = fopen ( " segments.txt " , " a " ) ;
while ( i < numFrames ) {
int ih = 0 ;
const int chunkw = width / piecesX ;
const int chunkh = height / piecesY ;
while ( ih < piecesY ) {
int iw = 0 ;
while ( iw < piecesX ) {
MagickWand * chunk = CloneMagickWand ( mw ) ;
MagickSetImageFormat ( chunk , " BMP3 " ) ;
MagickSetImageDepth ( chunk , 8 ) ;
MagickSetImageType ( chunk , PaletteType ) ;
MagickSetImageColorspace ( chunk , UndefinedColorspace ) ;
MagickCropImage ( chunk , chunkw , chunkh , ( chunkw * iw ) + ( width * i ) , chunkh * ih ) ;
printf ( " Saving frame #%d, chunk offset %d,%d \n " , i + 1 , iw , ih ) ;
2025-02-11 23:52:45 -06:00
char * framename = ( char * ) malloc ( 12 * piecesX * piecesY * sizeof ( char ) ) ; // MSVC
2025-02-11 02:51:24 -06:00
sprintf ( framename , " f%d%d%d.bmp " , i , iw , ih ) ;
fprintf ( partlist , " f%d%d%d.bmp \n " , i , iw , ih ) ;
MagickWriteImage ( chunk , framename ) ;
2025-02-18 01:08:53 -06:00
char * command = ( char * ) malloc ( 512 ) ; // lazy hack until magickwand is fixed
sprintf ( command , " mogrify -depth 8 -define bmp:format=bmp3 -type palette -compress none %s " , framename ) ;
system ( command ) ;
free ( command ) ;
2025-02-11 02:51:24 -06:00
iw + + ;
}
ih + + ;
}
i + + ;
}
fclose ( partlist ) ;
2025-02-18 01:23:33 -06:00
if ( ! opt . d ) {
2025-02-11 02:51:24 -06:00
// Pass on images to COMPIMG
printf ( " Encoding texture file... \n " ) ;
2025-02-12 14:57:36 -06:00
char * command = ( char * ) malloc ( ( 54 + 3 * abs ( 1 - opt . o ) + strlen ( filename ) ) * sizeof ( char ) ) ; // MSVC
2025-02-11 02:51:24 -06:00
char argTransparent [ 3 ] ;
if ( opt . o = = 0 ) sprintf ( argTransparent , " -t " ) ;
2025-02-12 00:27:40 -06:00
# ifdef _MSC_VER
sprintf ( command , " compimg.exe -ace -c%d -r0 -pt%s -emov -M%s +segments.txt " , opt . c + 1 , argTransparent , filename ) ;
# else
2025-02-11 02:51:24 -06:00
sprintf ( command , " ./compimg.exe -ace -c%d -r0 -pt%s -emov -M%s +segments.txt " , opt . c + 1 , argTransparent , filename ) ;
2025-02-12 00:27:40 -06:00
# endif
2025-02-12 14:57:36 -06:00
printf ( " %s \n " , command ) ;
2025-02-18 15:11:19 -06:00
if ( system ( command ) ) { printf ( " ERROR trying to run COMPIMG! \n Make sure that COMPIMG.EXE is inside of the folder you are working in. \n " ) ; }
2025-02-18 01:08:53 -06:00
2025-02-18 01:20:45 -06:00
printf ( " Cleaning up temporary files... \n " ) ;
while ( i < numFrames ) {
int ih = 0 ;
while ( ih < piecesY ) {
int iw = 0 ;
while ( iw < piecesX ) {
char * framename = ( char * ) malloc ( 12 * piecesX * piecesY * sizeof ( char ) ) ; // MSVC
sprintf ( framename , " f%d%d%d.bmp " , i , iw , ih ) ;
remove ( framename ) ;
free ( framename ) ;
2025-02-18 01:08:53 -06:00
2025-02-18 01:20:45 -06:00
iw + + ;
}
ih + + ;
2025-02-18 01:08:53 -06:00
}
2025-02-18 01:20:45 -06:00
i + + ;
2025-02-18 01:08:53 -06:00
}
}
2025-02-19 11:46:48 -06:00
remove ( " segments.txt " ) ;
2025-02-11 02:51:24 -06:00
2025-02-18 01:20:45 -06:00
printf ( " \n \n !!! DONE !!! \n " ) ;
if ( opt . d )
printf ( " Your avatar name is: %s%ds*%dh*%dv*.mov \n " , filename , numFrames , piecesX , piecesY ) ;
2025-02-11 02:51:24 -06:00
if ( mw ) mw = DestroyMagickWand ( mw ) ;
MagickWandTerminus ( ) ;
2025-02-11 23:52:45 -06:00
// don't clean up heap strings since the OS will reclaim the memory anyway
2025-02-11 02:51:24 -06:00
return 0 ;
}