emote2ss

Animated webp to spritesheets converting tool
git clone git://bsandro.tech/emote2ss
Log | Files | Refs | README | LICENSE

main.c (8803B)


      1 /**
      2  * Convert animated .webp into a .webp spritesheet of given size.
      3  */
      4 
      5 // to use the GNU version of basename() that doesn't modify its argument
      6 #define _GNU_SOURCE
      7 #define STB_IMAGE_WRITE_IMPLEMENTATION
      8 #include <stdio.h>
      9 #include "webp/decode.h"
     10 #include "webp/encode.h"
     11 #include "webp/demux.h"
     12 #include <stdbool.h>
     13 #include <stdlib.h>
     14 #include <libgen.h>
     15 #include <string.h>
     16 #include <strings.h>
     17 #include "stb_image_write.h"
     18 #include "arg.h"
     19 
     20 #if defined(__OpenBSD__) || defined(__NetBSD__)
     21 #include <sys/syslimits.h>
     22 #endif
     23 
     24 #ifdef __linux__
     25 #include <linux/limits.h>
     26 #endif
     27 
     28 #ifdef __APPLE__
     29 #include <limits.h>
     30 #endif
     31 
     32 char *argv0;
     33 
     34 typedef struct {
     35 	uint8_t *rgba;
     36 	int duration;
     37 	bool is_key;
     38 } DecodedFrame;
     39 
     40 typedef struct {
     41 	char *path;
     42 	int width, height, bgcolor;
     43 	DecodedFrame *frames;
     44 	uint32_t frame_count;
     45 	void *raw_mem;
     46 } AnimatedImage;
     47 
     48 int alloc_image(AnimatedImage *img, uint32_t frame_count) {
     49 	if (frame_count==0) return 1;
     50 	uint8_t *mem = NULL;
     51 	DecodedFrame *frames = NULL;
     52 	const uint64_t rgba_size = img->width*img->height*4; // 4 color channels
     53 	const uint64_t img_size = frame_count*rgba_size*sizeof(uint8_t);
     54 	const uint64_t frames_size = frame_count*sizeof(DecodedFrame);
     55 
     56 	mem = malloc(img_size);
     57 	frames = malloc(frames_size);
     58 	if (mem==NULL||frames==NULL) {
     59 		fputs("Memory allocation error", stderr);
     60 		return 1;
     61 	}
     62 
     63 	for (uint32_t i=0; i<frame_count; ++i) {
     64 		frames[i].rgba = mem+i*rgba_size;
     65 		frames[i].duration = 0;
     66 		frames[i].is_key = false;
     67 	}
     68 	img->frame_count = frame_count;
     69 	img->frames = frames;
     70 	img->raw_mem = mem;
     71 	return 0;
     72 }
     73 
     74 void free_image(AnimatedImage *img) {
     75 	free(img->frames);
     76 	free(img->raw_mem);
     77 }
     78 
     79 int read_file(const char *fname, const uint8_t **data, size_t *size) {
     80 	if (data==NULL||size==NULL||fname==NULL) return 1;
     81 	*data = NULL;
     82 	*size = 0;
     83 	FILE *infile = fopen(fname, "rb");
     84 	if (infile==NULL) {
     85 		fputs("Error opening file", stderr);
     86 		return 1;
     87 	}
     88 	fseek(infile, 0, SEEK_END);
     89 	size_t fsize = ftell(infile);
     90 	fseek(infile, 0, SEEK_SET);
     91 
     92 	uint8_t *fdata = malloc(fsize+1);
     93 	if (fdata==NULL) {
     94 		fputs("Memory allocation error", stderr);
     95 		return 1;
     96 	}
     97 	fdata[fsize] = '\0';
     98 	int ok = (fread(fdata, fsize, 1, infile)==1);
     99 	fclose(infile);
    100 
    101 	if (!ok) {
    102 		fprintf(stderr, "cannot read file %s (%d)\n", fname, ok);
    103 		free(fdata);
    104 		return 1;
    105 	}
    106 	*data = fdata;
    107 	*size = fsize;
    108 	return 0;
    109 }
    110 
    111 int read_webp(const char *fname, AnimatedImage *anim) {
    112 	WebPData webp_data;
    113 	WebPDataInit(&webp_data);
    114 
    115 	if (read_file(fname, &webp_data.bytes, &webp_data.size)!=0) {
    116 		fputs("Error reading file", stderr);
    117 		return 1;
    118 	}
    119 
    120 	if (!WebPGetInfo(webp_data.bytes, webp_data.size, NULL, NULL)) {
    121 		fputs("Error: invalid webp", stderr);
    122 		WebPDataClear(&webp_data);
    123 		return 1;
    124 	}
    125 
    126 	WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, NULL);
    127 	if (dec==NULL) {
    128 		fputs("Error decoding webp", stderr);
    129 		return 1;
    130 	}
    131 
    132 	WebPAnimInfo anim_info;
    133 	if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
    134 		fputs("Error decoding animation info", stderr);
    135 		WebPAnimDecoderDelete(dec);
    136 		return 1;
    137 	}
    138 
    139 	anim->width = anim_info.canvas_width;
    140 	anim->height = anim_info.canvas_height;
    141 	anim->bgcolor = anim_info.bgcolor;
    142 	alloc_image(anim, anim_info.frame_count);
    143 
    144 	uint32_t frame_index = 0;
    145 	int prev_ts = 0;
    146 	while (WebPAnimDecoderHasMoreFrames(dec)) {
    147 		DecodedFrame *frame;
    148 		uint8_t *curr_rgba, *frame_rgba;
    149 		int ts;
    150 		if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &ts)) {
    151 			fprintf(stderr, "Error decoding frame %d\n", frame_index);
    152 			return 1;
    153 		}
    154 		if (frame_index>=anim_info.frame_count) break;
    155 		frame = &anim->frames[frame_index];
    156 		curr_rgba = frame->rgba;
    157 		frame->duration = ts-prev_ts;
    158 		memcpy(curr_rgba, frame_rgba, anim->width*anim->height*4); // 4 color channels
    159 		// ... <- nani kore?
    160 		++frame_index;
    161 		prev_ts = ts;
    162 	}
    163 	WebPAnimDecoderReset(dec);
    164 	WebPDataClear(&webp_data);
    165 	WebPAnimDecoderDelete(dec);
    166 	printf("Input: %s: %dx%dpx x %d frames\n", anim->path, anim->width, anim->height, anim->frame_count);
    167 	return 0;
    168 }
    169 
    170 #define CHECK_EXT(out,out_len,ext,ext_len) ((out_len)>(ext_len)&&strncmp((out)+(out_len)-(ext_len),(ext),(ext_len))==0)
    171 
    172 int write_webp(const char *fname, uint8_t *data, int w, int h, int stride) {
    173 	uint8_t *out;
    174 	size_t encoded = WebPEncodeLosslessRGBA(data, w, h, stride, &out);
    175 	if (encoded==0) {
    176 		fputs("Error encoding webp", stderr);
    177 		return 1;
    178 	}
    179 	FILE *fp = fopen(fname, "wb");
    180 	if (fp==NULL) {
    181 		fputs("Error opening output file", stderr);
    182 		return 1;
    183 	}
    184 	size_t written = fwrite(out, sizeof(uint8_t), encoded, fp);
    185 	if (written!=encoded) {
    186 		fputs("Error writing output file", stderr);
    187 		return 1;
    188 	}
    189 	WebPFree(out);
    190 	fclose(fp);
    191 	return 0;
    192 }
    193 
    194 int write_pam(AnimatedImage *img, const char *fname, int cols) {
    195 	int rows = (int)img->frame_count/cols;
    196 	if ((int)img->frame_count%cols > 0) rows++;
    197 	FILE *fp = fopen(fname, "wb");
    198 	if (fp==NULL) {
    199 		fputs("Error opening output file", stderr);
    200 		return 1;
    201 	}
    202 	fprintf(fp, "P7\n");
    203 	fprintf(fp, "WIDTH %d\n", img->width*cols);
    204 	fprintf(fp, "HEIGHT %d\n", img->height*rows);
    205 	fprintf(fp, "DEPTH 4\n");
    206 	fprintf(fp, "MAXVAL 255\n");
    207 	fprintf(fp, "TUPLTYPE RGB_ALPHA\n");
    208 	fprintf(fp, "ENDHDR\n");
    209 
    210 	size_t line_size = img->width*sizeof(uint32_t);
    211 	for (int row=0; row<rows; ++row) {
    212 		for (int y=0; y<img->height; ++y) {
    213 			for (int col=0; col<cols; ++col) {
    214 				uint32_t offset=row*cols+col;
    215 				if (offset<img->frame_count) {
    216 					fwrite(img->frames[offset].rgba+y*line_size, sizeof(uint8_t), line_size, fp);
    217 				}
    218 			}
    219 		}
    220 	}
    221 	fclose(fp);
    222 	printf("Output: %s: %dx%dpx\n", fname, img->width*cols, img->height*rows);
    223 	return 0;
    224 }
    225 
    226 int write_ss(AnimatedImage *img, const char *fname, int cols) {
    227 	int rows = (int)img->frame_count/cols;
    228 	if ((int)img->frame_count%cols > 0) rows++;
    229 	size_t frame_size = img->width*img->height*sizeof(uint32_t);
    230 	size_t elem_size = img->width*sizeof(uint32_t);
    231 	size_t stride = elem_size*cols;
    232 	int width = img->width*cols;
    233 	int height = img->height*rows;
    234 	uint8_t *merged_orig = calloc(rows*cols, frame_size);
    235 	if (merged_orig==NULL) {
    236 		fputs("Memory allocation error", stderr);
    237 		return 1;
    238 	}
    239 	uint8_t *merged = merged_orig;
    240 	for (int row=0; row<rows; ++row) {
    241 		for (int y=0; y<img->height; ++y) {
    242 			for (int col=0; col<cols; ++col) {
    243 				uint32_t offset=row*cols+col;
    244 				if (offset<img->frame_count) {
    245 					memcpy(merged, img->frames[offset].rgba+y*elem_size, elem_size);
    246 				}
    247 				merged += elem_size;
    248 			}
    249 		}
    250 	}
    251 	printf("Output: %s: %dx%dpx\n", fname, width, height);
    252 	int ret = 1;
    253 	int fname_len = strlen(fname);
    254 	if (CHECK_EXT(fname, fname_len, ".webp", 5))
    255 		ret = write_webp(fname, merged_orig, width, height, stride);
    256 	else if (CHECK_EXT(fname, fname_len, ".png", 4))
    257 		ret = stbi_write_png(fname, width, height, 4, merged_orig, stride)==0;
    258 	else if (CHECK_EXT(fname, fname_len, ".bmp", 4))
    259 		ret = stbi_write_bmp(fname, width, height, 4, merged_orig)==0;
    260 	else if (CHECK_EXT(fname, fname_len, ".tga", 4))
    261 		ret = stbi_write_tga(fname, width, height, 4, merged_orig)==0;
    262 	else if (CHECK_EXT(fname, fname_len, ".jpg", 4))
    263 		ret = stbi_write_jpg(fname, width, height, 4, merged_orig, 0)==0;
    264 	else
    265 		fputs("Unknown output format", stderr);
    266 	free(merged_orig);
    267 	return ret;
    268 }
    269 
    270 int save_file(AnimatedImage *img, char *out, int cols, int width) {
    271 	char out_gen[PATH_MAX] = {0};
    272 	if (out==NULL) {
    273 		char *in_name = basename(img->path);
    274 		char *in_dir = dirname(img->path);
    275 		int n = snprintf(out_gen, PATH_MAX-1, "%s/atlas_%s", in_dir, in_name);
    276 		if (n<=0) {
    277 			fputs("Error generating output name", stderr);
    278 			return 1;
    279 		}
    280 		out = out_gen;
    281 	}
    282 	if (cols==0&&width==0) {
    283 		fputs("Either columns or width parameter has to be set", stderr);
    284 		return 1;
    285 	} else if (cols==0) {
    286 		cols = width/img->width;
    287 	}
    288 	if (cols==0) {
    289 		fputs("Invalid width", stderr);
    290 		return 1;
    291 	}
    292 	if (CHECK_EXT(out, strlen(out), ".pam", 4))
    293 		return write_pam(img, out, cols);
    294 	else
    295 		return write_ss(img, out, cols);
    296 }
    297 
    298 void print_usage(int code) {
    299 	printf("Usage: %s [-w MAX_WIDTH_PIXELS] [-c COLUMNS] [-o OUTPUT_FILE] input_file.webp\n", argv0);
    300 	exit(code);
    301 }
    302 
    303 int main(int argc, char **argv) {
    304 	char *in_path = argv[argc-1];
    305 	char *out_path = NULL;
    306 	int cols = 0;
    307 	int max_width = 0;
    308 
    309 	ARGBEGIN {
    310 	case 'o':
    311 		out_path = EARGF(print_usage(1));
    312 		break;
    313 	case 'c':
    314 		cols = atoi(EARGF(print_usage(1)));
    315 		break;
    316 	case 'w':
    317 		max_width = atoi(EARGF(print_usage(1)));
    318 		break;
    319 	case 'h':
    320 		print_usage(0);
    321 		break;
    322 	default:
    323 		print_usage(1);
    324 	} ARGEND;
    325 
    326 	if (in_path==NULL||(cols<=0 && max_width<=0)) {
    327 		puts("invalid arguments");
    328 		print_usage(1);
    329 		return 1;
    330 	}
    331 	AnimatedImage img = { .path=in_path };
    332 	int ret = 0;
    333 	if (read_webp(in_path, &img)!=0) {
    334 		fputs("Error parsing WEBP", stderr);
    335 		ret = 1;
    336 	}
    337 	if (save_file(&img, out_path, cols, max_width)!=0) {
    338 		fputs("Error saving file", stderr);
    339 		ret = 1;
    340 	}
    341 	free_image(&img);
    342 	return ret;
    343 }