emote2ss

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

main.c (6026B)


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