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:
| M | LICENSE | | | 2 | +- |
| M | README | | | 20 | +++++++++++++------- |
| M | cli/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;
}