From 891606c85357581e79dffe96f72fac2de0831890 Mon Sep 17 00:00:00 2001 From: Downforce Agent Date: Tue, 11 Feb 2025 02:51:24 -0600 Subject: [PATCH] Initial Commit --- .gitignore | 2 + CMakeLists.txt | 19 ++++ main.c | 260 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 281 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9ac99f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/cmake-build-debug/ +.idea \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..79dd346 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.31) +project(kavat C) + +set(CMAKE_C_STANDARD 11) + +add_executable(kavat main.c) + +FIND_PACKAGE(ImageMagick + REQUIRED + COMPONENTS MagickWand +) + +add_compile_definitions(MAGICKCORE_HDRI_ENABLE=0) +add_compile_definitions(MAGICKCORE_QUANTUM_DEPTH=8) + +set(CMAKE_SYSTEM_NAME Linux) +target_include_directories(kavat PUBLIC ${ImageMagick_INCLUDE_DIRS} ${ImageMagick_MagickWand_INCLUDE_DIRS}) +target_link_libraries(kavat m ${ImageMagick_LIBRARIES} ${ImageMagick_MagickWand_LIBRARY}) +target_link_directories(kavat PUBLIC ${ImageMagick_INCLUDE_DIRS} ${ImageMagick_MagickWand_INCLUDE_DIRS}) diff --git a/main.c b/main.c new file mode 100644 index 0000000..ab1f8cc --- /dev/null +++ b/main.c @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include + +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 { + int r; + int s; + uint8_t c; + int o; + int l; + int w; + int h; +}; + +void printHelp() { + printf( + "usage: kavat [options]\n" + "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" + "THIS PROGRAM IS DESIGNED FOR HOLOGRAMS ONLY. If you are making world textures.\n" + "you best learn how to run COMPIMG manually, please see https://kangworlds.net/tutorials/cmp\n" + "\n" + "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" +// " -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." + //" -h Same as -w, but vertical. If used with -w, KAvaT will create each chunk\n" + //" from each row left-to-right first, not each column top-to-bottom.\n" + ); +} + +void printNoFrames() { + printf("You need to specify at least one frame to use, goober.\n"); +} + +int main(const int argc, char *argv[]) { + printf( + "Knowledge Adventure Worlds Avatar Tool, version 1\n" + "Copyright (c) 2025 Brett \"bonkmaykr\" Bergstrom\n" + "This program is free software under the MIT license.\n" + "\n" + ); + if (argc < 2) { printHelp(); return 0; } + char filename[strlen(argv[1])]; strcpy(filename, argv[1]); + 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; + opt.c = 255; + opt.o = 0; + opt.l = 0; + opt.w = 0; + opt.h = 0; + uint16_t numFrames = 0; + char* frames[360]; + + int i = 2; + while (i < argc) { + if (argv[i][0] == '+') { + 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]); + + numFrames++; + } else if (argv[i] == "-r") { + + } else if (strcmp(argv[i], "-s") == 0) { + opt.s = 1; + } else if (argv[i] == "-o") { + + } else if (argv[i] == "-b") { + + } else { + printf("I don't know what \"%s\" means!\n", argv[i]); + return 0; + } + i++; + } + printf("Importing %d frames...\n", numFrames); + 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); + + 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); + 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); + char framename[12*piecesX*piecesY]; // this is stupid + sprintf(framename, "f%d%d%d.bmp", i, iw, ih); + fprintf(partlist, "f%d%d%d.bmp\n", i, iw, ih); + + MagickWriteImage(chunk, framename); + + char command[57 + 12*piecesX*piecesY]; // retard hack :)))))) because magickwand can't just do what i fucking ask + sprintf(command, "mogrify -depth 8 -define bmp:format=bmp3 -type palette %s", framename); + if (system(command)) { printf("ERROR trying to run mogrify! Is imagemagick installed to your PATH?\n"); return 0; } + + iw++; + } + ih++; + } + i++; + } + fclose(partlist); + + // Pass on images to COMPIMG + printf("Encoding texture file...\n"); + char command[54 + 3*opt.o + strlen(filename)]; + char argTransparent[3]; + if (opt.o == 0) sprintf(argTransparent, " -t"); + sprintf(command, "./compimg.exe -ace -c%d -r0 -pt%s -emov -M%s +segments.txt", opt.c+1, argTransparent, filename); + if (system(command)) { printf("ERROR trying to run COMPIMG!\n"); return 0; } + + printf("\n\n!!! DONE !!!\nYour avatar name is: %s%ds*%dh*%dv*.mov\n", filename, numFrames, piecesX, piecesY); + + if(mw) mw = DestroyMagickWand(mw); + MagickWandTerminus(); + return 0; +} \ No newline at end of file