Initial Commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/cmake-build-debug/
|
||||
.idea
|
||||
19
CMakeLists.txt
Normal file
19
CMakeLists.txt
Normal file
@@ -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})
|
||||
260
main.c
Normal file
260
main.c
Normal file
@@ -0,0 +1,260 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <MagickWand/MagickWand.h>
|
||||
|
||||
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 <target MOV> <source images> [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;
|
||||
}
|
||||
Reference in New Issue
Block a user