emote2ss

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

main.c (5928B)


      1 /**
      2  * Based on the libwebp example program "anim_dump"
      3  */
      4 
      5 #define _GNU_SOURCE
      6 
      7 #include <stdio.h>
      8 #include "webp/decode.h"
      9 #include "webp/encode.h"
     10 #include "webp/demux.h"
     11 #include <stdbool.h>
     12 #include <stdlib.h>
     13 #include <libgen.h>
     14 #include <string.h>
     15 #include <strings.h>
     16 #include <assert.h>
     17 
     18 #ifdef __OpenBSD__
     19 #include <sys/syslimits.h>
     20 #endif
     21 
     22 #ifdef __linux__
     23 #include <linux/limits.h>
     24 #endif
     25 
     26 #define NUM_CHANNELS 4
     27 
     28 static void print_webp_version() {
     29 	int dec_ver = WebPGetDecoderVersion();
     30 	int demux_ver = WebPGetDemuxVersion();
     31 	printf("---------------------------\n");
     32 	printf("webp decoder version: %d.%d.%d\n", (dec_ver>>16)&0xff, (dec_ver>>8)&0xff, dec_ver&0xff);
     33 	printf("webp demuxer version: %d.%d.%d\n", (demux_ver>>16)&0xff, (demux_ver>>8)&0xff, demux_ver&0xff);
     34 }
     35 
     36 typedef struct {
     37 	uint8_t *rgba;
     38 	int duration;
     39 	bool is_key;
     40 } DecodedFrame;
     41 
     42 typedef struct {
     43 	int width, height, bgcolor, loop_count;
     44 	DecodedFrame *frames;
     45 	uint32_t frame_count;
     46 	void *raw_mem;
     47 } AnimatedImage;
     48 
     49 void alloc_image(AnimatedImage *img, uint32_t frame_count) {
     50 	assert(frame_count > 0);
     51 	uint8_t *mem = NULL;
     52 	DecodedFrame *frames = NULL;
     53 	const uint64_t rgba_size = img->width * img->height * NUM_CHANNELS;
     54 	const uint64_t img_size = frame_count * rgba_size * sizeof(uint8_t);
     55 	const uint64_t frames_size = frame_count * sizeof(DecodedFrame);
     56 
     57 	assert(img_size == (size_t)img_size);
     58 	assert(frames_size == (size_t)frames_size);
     59 
     60 	printf("img mem: %" PRIu64 "\nframes mem: %" PRIu64 "\n", img_size, frames_size);
     61 
     62 	mem = malloc(img_size);
     63 	frames = malloc(frames_size);
     64 	assert(mem != NULL);
     65 	assert(frames != NULL);
     66 
     67 	for (uint32_t i=0; i<frame_count; ++i) {
     68 		frames[i].rgba = mem+i*rgba_size;
     69 		frames[i].duration = 0;
     70 		frames[i].is_key = false;
     71 	}
     72 	img->frame_count = frame_count;
     73 	img->frames = frames;
     74 	img->raw_mem = mem;
     75 }
     76 
     77 int read_file(const char *fname, const uint8_t **data, size_t *size) {
     78 	assert(data != NULL);
     79 	assert(size != NULL);
     80 
     81 	*data = NULL;
     82 	*size = 0;
     83 	FILE *infile = fopen(fname, "rb");
     84 	assert(infile != NULL);
     85 	fseek(infile, 0, SEEK_END);
     86 	size_t fsize = ftell(infile);
     87 	printf("%s: %zu bytes\n", fname, fsize);
     88 	fseek(infile, 0, SEEK_SET);
     89 
     90 	uint8_t *fdata = malloc(fsize+1);
     91 	assert(fdata != NULL);
     92 	fdata[fsize] = '\0';
     93 	int ok = (fread(fdata, fsize, 1, infile)==1);
     94 	fclose(infile);
     95 
     96 	if (!ok) {
     97 		fprintf(stderr, "cannot read file %s (%d)\n", fname, ok);
     98 		free(fdata);
     99 		return -1;
    100 	}
    101 	*data = fdata;
    102 	*size = fsize;
    103 	return 0;
    104 }
    105 
    106 int read_webp(const char *fname, AnimatedImage *anim) {
    107 	printf("read_webp(%s)\n", fname);
    108 
    109 	WebPData webp_data;
    110 	WebPDataInit(&webp_data);
    111 
    112 	if (read_file(fname, &webp_data.bytes, &webp_data.size) == -1) {
    113 		fprintf(stderr, "read_file error\n");
    114 		return -1;
    115 	}
    116 
    117 	if (!WebPGetInfo(webp_data.bytes, webp_data.size, NULL, NULL)) {
    118 		fprintf(stderr, "invalid webp\n");
    119 		WebPDataClear(&webp_data);
    120 		return -1;
    121 	}
    122 
    123 	WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, NULL);
    124 	assert(dec != NULL);
    125 
    126 	WebPAnimInfo anim_info;
    127 	if (!WebPAnimDecoderGetInfo(dec, &anim_info)) {
    128 		fprintf(stderr, "error decoding animation info\n");
    129 		// @todo cleanup
    130 		return -1;
    131 	}
    132 
    133 	anim->width = anim_info.canvas_width;
    134 	anim->height = anim_info.canvas_height;
    135 	anim->loop_count = anim_info.loop_count;
    136 	anim->bgcolor = anim_info.bgcolor;
    137 	alloc_image(anim, anim_info.frame_count);
    138 
    139 	uint32_t frame_index = 0;
    140 	int prev_ts = 0;
    141 	while (WebPAnimDecoderHasMoreFrames(dec)) {
    142 		DecodedFrame *frame;
    143 		uint8_t *curr_rgba, *frame_rgba;
    144 		int ts;
    145 		if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &ts)) {
    146 			fprintf(stderr, "error decoding frame %d\n", frame_index);
    147 			return -1;
    148 		}
    149 		assert(frame_index < anim_info.frame_count);
    150 		frame = &anim->frames[frame_index];
    151 		curr_rgba = frame->rgba;
    152 		frame->duration = ts - prev_ts;
    153 		memcpy(curr_rgba, frame_rgba, anim->width * anim->height * NUM_CHANNELS);
    154 		// ... <- nani kore?
    155 		++frame_index;
    156 		prev_ts = ts;
    157 	}
    158 
    159 	WebPDataClear(&webp_data);
    160 	WebPAnimDecoderDelete(dec);
    161 	return 0;
    162 }
    163 
    164 void write_webp(const char *fname, AnimatedImage *img, int cols) {
    165 	int rows = (int)img->frame_count / cols;
    166 	if ((int)img->frame_count % cols > 0) {
    167 		++rows;
    168 	}
    169 	FILE *fp = fopen(fname, "wb");
    170 	assert(fp!=NULL);
    171 	uint8_t *out;
    172 	size_t frame_size = img->width * img->height * sizeof(uint32_t);
    173 	size_t line_size = img->width * sizeof(uint32_t);
    174 	size_t full_line = line_size * cols;
    175 	uint8_t *merged = calloc(rows * cols, frame_size);
    176 	assert(merged!=NULL);
    177 	uint8_t *merged_orig = merged;
    178 	for (int row = 0; row < rows; ++row) {
    179 		for (int y = 0; y < img->height; ++y) {
    180 			for (int col = 0; col < cols; ++col) {
    181 				uint32_t offset = row*cols+col;
    182 				if (offset < img->frame_count) {
    183 					memcpy(merged, img->frames[offset].rgba+y*line_size, line_size);
    184 				}
    185 				merged += line_size;
    186 			}
    187 		}
    188 	}
    189 	int stride = full_line;
    190 	printf("stride: %d\n", stride);
    191 	size_t encoded = WebPEncodeLosslessRGBA(merged_orig, img->width * cols, img->height * rows, stride, &out);
    192 	printf("size: %zu, encoded: %zu\n", img->width*img->height*sizeof(uint32_t), encoded);
    193 	assert(encoded!=0);
    194 	size_t written = fwrite(out, sizeof(uint8_t), encoded, fp);
    195 	assert(written==encoded);
    196 	WebPFree(out);
    197 	free(merged_orig);
    198 	fclose(fp);
    199 }
    200 
    201 int main(int argc, const char **argv) {
    202 	atexit(print_webp_version);
    203 
    204 	if (argc < 3) {
    205 		printf("Usage: %s anim_file.webp COLUMNS\n", argc>0?argv[0]:"emote2ss");
    206 		return 0;
    207 	}
    208 
    209 	AnimatedImage img = {0};
    210 	char out_name[PATH_MAX];
    211 	char *in_path = strndup(argv[1], PATH_MAX-1);
    212 	assert(in_path!=NULL);
    213 	int cols = atoi(argv[2]);
    214 	int r = read_webp(in_path, &img);
    215 	assert(r==0);
    216 	char *in_name = basename((char *)in_path);
    217 	char *in_dir = dirname((char *)in_path);
    218 	int n = snprintf(out_name, NAME_MAX, "atlas_%s", in_name);
    219 	assert(n>0);
    220 	printf("[path:%s][%s -> %s(%d)]\ndimensions: %dx%d\nframes: %d\n", in_dir, in_name, out_name, cols, img.width, img.height, img.frame_count);
    221 	write_webp(out_name, &img, cols);
    222 
    223 	return 0;
    224 }