emote2ss

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

commit 395b38542cc2df7c8ea74c3a0e13bd6cd6c656d9
parent 249a954276e6e2317a9a908946591403a46e1dc7
Author: bsandro <email@bsandro.tech>
Date:   Fri,  2 Jan 2026 20:31:37 +0200

Cleaned up CLI version; -w and -o command-line arguments are working now

Diffstat:
MLICENSE | 2+-
MREADME | 20+++++++++++++-------
Mcli/main.c | 103++++++++++++++++++++++++++++++++-----------------------------------------------
3 files changed, 56 insertions(+), 69 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2023, bsandro. All rights reserved. +Copyright (c) 2026, bsandro. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README b/README @@ -2,12 +2,17 @@ emote2ss = 'Emote to SpriteSheet' utility. License: BSD 2-Clause. -Simple tool to expand a given animated .webp into a spritesheet of given columns count-wide. +Simple tool to expand a given animated .webp into a spritesheet of given columns count-wide of lossless webp. There is a GUI version with a preview of the output sprite and a command-line utility that you can pass the column count as a command-line argument. CLI Usage: -`emote2ss file.webp COLUMNS`, e.g. `emote2ss MyAnimationFile.webp 10` - expands animated file into a spritesheet of 10 frames wide. The output file is named the same as the source with the prefix "atlas_", e.g. FlanClap.webp produces atlas_FlanClap.webp +`emote2ss [-c COLUMNS] [-w MAX_WIDTH_PIXELS] [-o OUTPUT_FILE] file.webp`, e.g. `emote2ss -c 10 MyAnimationFile.webp` - expands animated file into a spritesheet of 10 frames wide. +Other arguments: +-w MAX_WIDTH - as alternative to columns it is possible to pass maximum spritesheet width in pixels. The utility will fit as many columns there as possible. +-o OUTPUT_FILE - optional path+name for output file. If it is not set then the output file is named the same as the source with the prefix "atlas_", e.g. FlanClap.webp produces atlas_FlanClap.webp at the same directory as source. + +Note that at least one of the options -c or -w has to be set. GUI Usage: should be self-explanatory, it doesn't support command-line arguments as of yet. @@ -15,12 +20,13 @@ Dependencies: Libraries: libwebp and libwebpdemux. Bundled GUI toolkit: luigi - https://github.com/nakst/luigi -Building: -Only libraries above, C99 compiler, standard pkg-config and GNU Makefile are needed. -I've tested builds for GNU/Linux, OpenBSD and macOS. +Building prerequisities: +Only libwebp/libwebpdemux, C99 compiler, standard pkg-config and GNU Makefile are needed. +I've tested builds for GNU/Linux, OpenBSD, NetBSD and macOS. Windows builds are still WIP, it was sufficient to change '-DUI_LINUX' to '-DUI_WINDOWS' in gui/Makefile before but not after many changes in gui version. -To build GUI tool run `make gui`. -To build CLI tool run `make cli`. +To build GUI tool run `make gui` or run make inside 'gui' folder. +To build CLI tool run `make cli` or run make inside 'cli' folder. + Or just run `make` to build everything. diff --git a/cli/main.c b/cli/main.c @@ -1,5 +1,5 @@ /** - * Based on the libwebp example program "anim_dump" + * Convert animated .webp into a .webp spritesheet of given size. */ // to use the GNU version of basename() that doesn't modify its argument @@ -16,11 +16,7 @@ #include <strings.h> #include "arg.h" -#ifdef __OpenBSD__ -#include <sys/syslimits.h> -#endif - -#ifdef __NetBSD__ +#if defined(__OpenBSD__) || defined(__NetBSD__) #include <sys/syslimits.h> #endif @@ -32,18 +28,8 @@ #include <limits.h> #endif -#define NUM_CHANNELS 4 - char *argv0; -static void print_webp_version(void) { - int dec_ver = WebPGetDecoderVersion(); - int demux_ver = WebPGetDemuxVersion(); - printf("---------------------------\n"); - printf("webp decoder version: %d.%d.%d\n", (dec_ver>>16)&0xff, (dec_ver>>8)&0xff, dec_ver&0xff); - printf("webp demuxer version: %d.%d.%d\n", (demux_ver>>16)&0xff, (demux_ver>>8)&0xff, demux_ver&0xff); -} - typedef struct { uint8_t *rgba; int duration; @@ -52,7 +38,7 @@ typedef struct { typedef struct { char *path; - int width, height, bgcolor, loop_count; + int width, height, bgcolor; DecodedFrame *frames; uint32_t frame_count; void *raw_mem; @@ -62,10 +48,9 @@ int alloc_image(AnimatedImage *img, uint32_t frame_count) { if (frame_count==0) return 1; uint8_t *mem = NULL; DecodedFrame *frames = NULL; - const uint64_t rgba_size = img->width * img->height * NUM_CHANNELS; - const uint64_t img_size = frame_count * rgba_size * sizeof(uint8_t); - const uint64_t frames_size = frame_count * sizeof(DecodedFrame); - printf("img mem: %" PRIu64 "\nframes mem: %" PRIu64 "\n", img_size, frames_size); + const uint64_t rgba_size = img->width*img->height*4; // 4 color channels + const uint64_t img_size = frame_count*rgba_size*sizeof(uint8_t); + const uint64_t frames_size = frame_count*sizeof(DecodedFrame); mem = malloc(img_size); frames = malloc(frames_size); @@ -101,7 +86,6 @@ int read_file(const char *fname, const uint8_t **data, size_t *size) { } fseek(infile, 0, SEEK_END); size_t fsize = ftell(infile); - printf("%s: %zu bytes\n", fname, fsize); fseek(infile, 0, SEEK_SET); uint8_t *fdata = malloc(fsize+1); @@ -124,8 +108,6 @@ int read_file(const char *fname, const uint8_t **data, size_t *size) { } int read_webp(const char *fname, AnimatedImage *anim) { - printf("read_webp(%s)\n", fname); - WebPData webp_data; WebPDataInit(&webp_data); @@ -142,6 +124,7 @@ int read_webp(const char *fname, AnimatedImage *anim) { WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, NULL); if (dec==NULL) { + fputs("Error decoding webp", stderr); return 1; } @@ -154,7 +137,6 @@ int read_webp(const char *fname, AnimatedImage *anim) { anim->width = anim_info.canvas_width; anim->height = anim_info.canvas_height; - anim->loop_count = anim_info.loop_count; anim->bgcolor = anim_info.bgcolor; alloc_image(anim, anim_info.frame_count); @@ -171,8 +153,8 @@ int read_webp(const char *fname, AnimatedImage *anim) { if (frame_index>=anim_info.frame_count) break; frame = &anim->frames[frame_index]; curr_rgba = frame->rgba; - frame->duration = ts - prev_ts; - memcpy(curr_rgba, frame_rgba, anim->width * anim->height * NUM_CHANNELS); + frame->duration = ts-prev_ts; + memcpy(curr_rgba, frame_rgba, anim->width*anim->height*4); // 4 color channels // ... <- nani kore? ++frame_index; prev_ts = ts; @@ -180,32 +162,33 @@ int read_webp(const char *fname, AnimatedImage *anim) { WebPAnimDecoderReset(dec); WebPDataClear(&webp_data); WebPAnimDecoderDelete(dec); + printf("Input: %s: %dx%dpx x %d frames\n", anim->path, anim->width, anim->height, anim->frame_count); return 0; } int write_webp(AnimatedImage *img, const char *fname, int cols) { - int rows = (int)img->frame_count / cols; - if ((int)img->frame_count % cols > 0) rows++; + int rows = (int)img->frame_count/cols; + if ((int)img->frame_count%cols > 0) rows++; FILE *fp = fopen(fname, "wb"); if (fp==NULL) { fputs("Error opening output file", stderr); return 1; } uint8_t *out; - size_t frame_size = img->width * img->height * sizeof(uint32_t); - size_t line_size = img->width * sizeof(uint32_t); - size_t full_line = line_size * cols; - uint8_t *merged_orig = calloc(rows * cols, frame_size); + size_t frame_size = img->width*img->height*sizeof(uint32_t); + size_t line_size = img->width*sizeof(uint32_t); + size_t full_line = line_size*cols; + uint8_t *merged_orig = calloc(rows*cols, frame_size); if (merged_orig==NULL) { fputs("Memory allocation error", stderr); return 1; } uint8_t *merged = merged_orig; - for (int row = 0; row < rows; ++row) { - for (int y = 0; y < img->height; ++y) { - for (int col = 0; col < cols; ++col) { - uint32_t offset = row*cols+col; - if (offset < img->frame_count) { + for (int row=0; row<rows; ++row) { + for (int y=0; y<img->height; ++y) { + for (int col=0; col<cols; ++col) { + uint32_t offset=row*cols+col; + if (offset<img->frame_count) { memcpy(merged, img->frames[offset].rgba+y*line_size, line_size); } merged += line_size; @@ -213,9 +196,7 @@ int write_webp(AnimatedImage *img, const char *fname, int cols) { } } int stride = full_line; - printf("stride: %d\n", stride); size_t encoded = WebPEncodeLosslessRGBA(merged_orig, img->width*cols, img->height*rows, stride, &out); - printf("size: %zu, encoded: %zu\n", img->width*img->height*sizeof(uint32_t), encoded); if (encoded==0) { fputs("Error encoding webp", stderr); return 1; @@ -228,20 +209,33 @@ int write_webp(AnimatedImage *img, const char *fname, int cols) { WebPFree(out); free(merged_orig); fclose(fp); + printf("Output: %s: %dx%dpx\n", fname, img->width*cols, img->height*rows); return 0; } int save_file(AnimatedImage *img, char *out, int cols, int width) { - char out_name[NAME_MAX]; - char *in_name = basename(img->path); - char *in_dir = dirname(img->path); - int n = snprintf(out_name, NAME_MAX-1, "atlas_%s", in_name); - if (n<=0) { - fputs("Error generating output name", stderr); + char out_gen[PATH_MAX] = {0}; + if (out==NULL) { + char *in_name = basename(img->path); + char *in_dir = dirname(img->path); + int n = snprintf(out_gen, PATH_MAX-1, "%s/atlas_%s", in_dir, in_name); + if (n<=0) { + fputs("Error generating output name", stderr); + return 1; + } + out = out_gen; + } + if (cols==0&&width==0) { + fputs("Either columns or width parameter has to be set", stderr); return 1; + } else if (cols==0) { + cols = width/img->width; } - printf("[path:%s][%s -> %s(%d)]\ndimensions: %dx%d\nframes: %d\nout: %s\nmax width: %d\n", in_dir, in_name, out_name, cols, img->width, img->height, img->frame_count, out, width); - return write_webp(img, out_name, cols); + if (cols==0) { + fputs("Invalid width", stderr); + return 1; + } + return write_webp(img, out, cols); } void print_usage(void) { @@ -250,13 +244,10 @@ void print_usage(void) { } int main(int argc, char **argv) { - atexit(print_webp_version); - char *in_path = argv[argc-1]; char *out_path = NULL; int cols = 0; int max_width = 0; - printf("argc:%d\n", argc); ARGBEGIN { case 'o': @@ -272,30 +263,20 @@ int main(int argc, char **argv) { print_usage(); } ARGEND; - printf("in_path: %s\n", in_path); - printf("out_path: %s\n", out_path); - printf("cols: %d\n", cols); - printf("max_width: %d\n", max_width); - if (in_path==NULL||(cols<=0 && max_width<=0)) { puts("invalid arguments"); print_usage(); return 1; } - AnimatedImage img = { .path=in_path }; - if (read_webp(in_path, &img)!=0) { fputs("Error parsing WEBP", stderr); return 1; } - if (save_file(&img, out_path, cols, max_width)!=0) { fputs("Error saving file", stderr); return 1; } - free_image(&img); - return 0; }