emote2ss

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

main.c (5973B)


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