Ennix/tools/texturepacker.c

250 lines
8.3 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_render.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
// #include "../libs/libcyaml/include/cyaml/cyaml.h"
typedef struct Image {
char* filename;
unsigned char* imageData;
int id, width, height, channels;
} Image;
typedef struct TexRect {
Image* img;
int x,y,w,h;
} TexRect;
typedef struct { Image* images; int imageCount; } ParseImages;
ParseImages parseImages(int argc, char* argv[]) {
Image* images = calloc(argc - 1, sizeof(Image));
if (images == NULL) {
fprintf(stderr, "Failed to alloc image data array");
exit(-1);
}
int imageCount = argc - 1;
for (int i = 0; i < imageCount; i++) {
Image* img = &images[i];
img->filename = argv[i+1];
int result = stbi_info(img->filename, &img->width, &img->height, &img->channels);
if (result != 1) {
char *errorMsg = "Cannot load image '%s': %s\n";
fprintf(stderr, errorMsg, img->filename, stbi_failure_reason());
exit(-1);
}
}
return (ParseImages) { images , imageCount };
}
typedef struct { int size; int totalHeight; int maxWidth; } PackTextures;
PackTextures packTextures(Image images[], TexRect texRects[], int imageCount) {
int totalh = 0, maxw = 0;
for (int i = 0; i < imageCount; i++) {
texRects[i] = (TexRect){
&images[i],
0, // Put them all on the 1st column for now
totalh,
images[i].width,
images[i].height,
};
totalh += images[i].height;
if (images[i].width > maxw) {
maxw = images[i].width;
}
}
int max = totalh > maxw ? totalh : maxw;
int exp = (int)log2(max) + 1;
int size = (int)pow(2, exp);
return (PackTextures) { size , totalh , maxw };
}
int loadTexture(Image* img) {
int w,h,c;
img->imageData = (unsigned char*)stbi_load(img->filename, &w, &h, &c, 4);
if (img->imageData == NULL) {
char *errorMsg = "Could not load data for image '%s': %s";
fprintf(stderr, errorMsg, img->filename, stbi_failure_reason());
// QUESTION: Should we clean up here? Return -1 instead so parent can clean up
exit(-1);
}
return 0;
}
void writeToAtlas(Image images[],
int image_count,
TexRect tex_rects[],
unsigned char* buf,
int maxw)
{
int channels = images[0].channels;
for (int i = 0; i < image_count; i++) {
TexRect rect = tex_rects[i];
int buf_row = rect.y;
int buf_col = rect.x;
int img_row = 0;
int img_col = 0;
while (img_row < rect.h) {
int buf_idx = (buf_row * maxw + buf_col) * channels;
int img_idx = (img_row * rect.w + img_col) * channels;
for (int j = 0; j < channels; j++) {
buf[buf_idx + j] = images[i].imageData[img_idx + j];
}
img_col++; buf_col++;
if (img_col >= rect.w) {
img_col = buf_col = 0;
img_row++; buf_row++;
}
}
}
}
int sdlTest(Image* images, int image_count, TexRect* tex_rects) {
SDL_Window* window = NULL;
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
printf("error initializing SDL: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("SDL Tutorial",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
1024,
1024,
SDL_WINDOW_SHOWN );
if (window == NULL) {
printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
return 1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, 0, SDL_RENDERER_ACCELERATED);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
SDL_Texture* texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STREAMING,
512, 512);
SDL_Event e;
bool quit = false;
unsigned int* pixels = calloc(512 * 512, sizeof(unsigned int));
int img_row = 0;
int img_col = 0;
int buf_row = 0;
int channels = images[0].channels;
int i = 0;
while (quit == false) {
while (SDL_PollEvent( &e)) {
if (e.type == SDL_QUIT) {
quit = true;
}
}
// for (int i = 0; i < image_count; i++) {
SDL_RenderClear(renderer);
if (i < image_count) {
for (int s = 0; s < 8; s++) {
TexRect rect = tex_rects[i];
unsigned char* img = images[i].imageData;
int img_idx = (img_row * rect.w + img_col) * channels;
int color = ((unsigned int)img[img_idx + 0]) << 24
| ((unsigned int)img[img_idx + 1]) << 16
| ((unsigned int)img[img_idx + 2]) << 8
| ((unsigned int)img[img_idx + 3]);
pixels[buf_row * 512 + img_col] = color;
img_col++;
if (img_col >= rect.w) {
img_col = 0;
img_row++; buf_row++;
}
if (img_row >= rect.h) {
i++;
img_row = 0;
}
}
}
SDL_UpdateTexture(texture, NULL, pixels, 512 * sizeof(unsigned int));
SDL_SetTextureBlendMode( texture, SDL_BLENDMODE_BLEND );
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
free(pixels);
return 0;
}
int main(int argc, char* argv[]) {
if (argc == 1) {
fprintf(stderr, "Must provide image files to pack");
exit(-1);
}
if (argc == 2) {
fprintf(stderr, "Please provide at least 2 images to pack");
exit(-1);
}
//-------------------------------------
// First get the image info, make sure the file is an image and it exists
//-------------------------------------
ParseImages pi = parseImages(argc, argv);
Image* images = pi.images;
int imageCount = pi.imageCount;
//-------------------------------------
// Now run the packing algorithm. This is super inefficient
// texture packing but I just want to get it working first
//-------------------------------------
TexRect* texRects = calloc(imageCount, sizeof(TexRect));
// int max = pack_textures(images, tex_rects, image_count, &size, &totalh, &maxw);
PackTextures pt = packTextures(images, texRects, imageCount);
int size = pt.size;
//-------------------------------------
// Now load the data into memory, for now, let's just load all images in one go,
// however, it would be more optimized if we loaded each image sequentially or
// better yet in parallel so that we can write to the different parts of the
// texture atlas concurrently
//-------------------------------------
for (int i = 0; i < imageCount; i++) {
int result = loadTexture(&images[i]);
if (result != 0) {
// It's going to exit() inside the load_texture function for now
printf("Something went wrong\n");
}
}
//-------------------------------------
// For each texture rect, draw the corresponding pixels on the atlas
//-------------------------------------
unsigned char* buf = calloc(size*size*4, sizeof(char));
writeToAtlas(images, imageCount, texRects, buf, size);
stbi_write_png("test.png", size, size, 4, buf, size * 4);
// sdlTest(images, imageCount, texRects);
//-------------------------------------
// Free them all, but eventually we would free after we finished writing them individually
//-------------------------------------
for (int i = 0; i < imageCount; i++) {
stbi_image_free(images[i].imageData);
}
free(buf);
free(images);
free(texRects);
return 0;
}