main.c (8803B)
1 /** 2 * Convert animated .webp into a .webp spritesheet of given size. 3 */ 4 5 // to use the GNU version of basename() that doesn't modify its argument 6 #define _GNU_SOURCE 7 #define STB_IMAGE_WRITE_IMPLEMENTATION 8 #include <stdio.h> 9 #include "webp/decode.h" 10 #include "webp/encode.h" 11 #include "webp/demux.h" 12 #include <stdbool.h> 13 #include <stdlib.h> 14 #include <libgen.h> 15 #include <string.h> 16 #include <strings.h> 17 #include "stb_image_write.h" 18 #include "arg.h" 19 20 #if defined(__OpenBSD__) || defined(__NetBSD__) 21 #include <sys/syslimits.h> 22 #endif 23 24 #ifdef __linux__ 25 #include <linux/limits.h> 26 #endif 27 28 #ifdef __APPLE__ 29 #include <limits.h> 30 #endif 31 32 char *argv0; 33 34 typedef struct { 35 uint8_t *rgba; 36 int duration; 37 bool is_key; 38 } DecodedFrame; 39 40 typedef struct { 41 char *path; 42 int width, height, bgcolor; 43 DecodedFrame *frames; 44 uint32_t frame_count; 45 void *raw_mem; 46 } AnimatedImage; 47 48 int alloc_image(AnimatedImage *img, uint32_t frame_count) { 49 if (frame_count==0) return 1; 50 uint8_t *mem = NULL; 51 DecodedFrame *frames = NULL; 52 const uint64_t rgba_size = img->width*img->height*4; // 4 color channels 53 const uint64_t img_size = frame_count*rgba_size*sizeof(uint8_t); 54 const uint64_t frames_size = frame_count*sizeof(DecodedFrame); 55 56 mem = malloc(img_size); 57 frames = malloc(frames_size); 58 if (mem==NULL||frames==NULL) { 59 fputs("Memory allocation error", stderr); 60 return 1; 61 } 62 63 for (uint32_t i=0; i<frame_count; ++i) { 64 frames[i].rgba = mem+i*rgba_size; 65 frames[i].duration = 0; 66 frames[i].is_key = false; 67 } 68 img->frame_count = frame_count; 69 img->frames = frames; 70 img->raw_mem = mem; 71 return 0; 72 } 73 74 void free_image(AnimatedImage *img) { 75 free(img->frames); 76 free(img->raw_mem); 77 } 78 79 int read_file(const char *fname, const uint8_t **data, size_t *size) { 80 if (data==NULL||size==NULL||fname==NULL) return 1; 81 *data = NULL; 82 *size = 0; 83 FILE *infile = fopen(fname, "rb"); 84 if (infile==NULL) { 85 fputs("Error opening file", stderr); 86 return 1; 87 } 88 fseek(infile, 0, SEEK_END); 89 size_t fsize = ftell(infile); 90 fseek(infile, 0, SEEK_SET); 91 92 uint8_t *fdata = malloc(fsize+1); 93 if (fdata==NULL) { 94 fputs("Memory allocation error", stderr); 95 return 1; 96 } 97 fdata[fsize] = '\0'; 98 int ok = (fread(fdata, fsize, 1, infile)==1); 99 fclose(infile); 100 101 if (!ok) { 102 fprintf(stderr, "cannot read file %s (%d)\n", fname, ok); 103 free(fdata); 104 return 1; 105 } 106 *data = fdata; 107 *size = fsize; 108 return 0; 109 } 110 111 int read_webp(const char *fname, AnimatedImage *anim) { 112 WebPData webp_data; 113 WebPDataInit(&webp_data); 114 115 if (read_file(fname, &webp_data.bytes, &webp_data.size)!=0) { 116 fputs("Error reading file", stderr); 117 return 1; 118 } 119 120 if (!WebPGetInfo(webp_data.bytes, webp_data.size, NULL, NULL)) { 121 fputs("Error: invalid webp", stderr); 122 WebPDataClear(&webp_data); 123 return 1; 124 } 125 126 WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, NULL); 127 if (dec==NULL) { 128 fputs("Error decoding webp", stderr); 129 return 1; 130 } 131 132 WebPAnimInfo anim_info; 133 if (!WebPAnimDecoderGetInfo(dec, &anim_info)) { 134 fputs("Error decoding animation info", stderr); 135 WebPAnimDecoderDelete(dec); 136 return 1; 137 } 138 139 anim->width = anim_info.canvas_width; 140 anim->height = anim_info.canvas_height; 141 anim->bgcolor = anim_info.bgcolor; 142 alloc_image(anim, anim_info.frame_count); 143 144 uint32_t frame_index = 0; 145 int prev_ts = 0; 146 while (WebPAnimDecoderHasMoreFrames(dec)) { 147 DecodedFrame *frame; 148 uint8_t *curr_rgba, *frame_rgba; 149 int ts; 150 if (!WebPAnimDecoderGetNext(dec, &frame_rgba, &ts)) { 151 fprintf(stderr, "Error decoding frame %d\n", frame_index); 152 return 1; 153 } 154 if (frame_index>=anim_info.frame_count) break; 155 frame = &anim->frames[frame_index]; 156 curr_rgba = frame->rgba; 157 frame->duration = ts-prev_ts; 158 memcpy(curr_rgba, frame_rgba, anim->width*anim->height*4); // 4 color channels 159 // ... <- nani kore? 160 ++frame_index; 161 prev_ts = ts; 162 } 163 WebPAnimDecoderReset(dec); 164 WebPDataClear(&webp_data); 165 WebPAnimDecoderDelete(dec); 166 printf("Input: %s: %dx%dpx x %d frames\n", anim->path, anim->width, anim->height, anim->frame_count); 167 return 0; 168 } 169 170 #define CHECK_EXT(out,out_len,ext,ext_len) ((out_len)>(ext_len)&&strncmp((out)+(out_len)-(ext_len),(ext),(ext_len))==0) 171 172 int write_webp(const char *fname, uint8_t *data, int w, int h, int stride) { 173 uint8_t *out; 174 size_t encoded = WebPEncodeLosslessRGBA(data, w, h, stride, &out); 175 if (encoded==0) { 176 fputs("Error encoding webp", stderr); 177 return 1; 178 } 179 FILE *fp = fopen(fname, "wb"); 180 if (fp==NULL) { 181 fputs("Error opening output file", stderr); 182 return 1; 183 } 184 size_t written = fwrite(out, sizeof(uint8_t), encoded, fp); 185 if (written!=encoded) { 186 fputs("Error writing output file", stderr); 187 return 1; 188 } 189 WebPFree(out); 190 fclose(fp); 191 return 0; 192 } 193 194 int write_pam(AnimatedImage *img, const char *fname, int cols) { 195 int rows = (int)img->frame_count/cols; 196 if ((int)img->frame_count%cols > 0) rows++; 197 FILE *fp = fopen(fname, "wb"); 198 if (fp==NULL) { 199 fputs("Error opening output file", stderr); 200 return 1; 201 } 202 fprintf(fp, "P7\n"); 203 fprintf(fp, "WIDTH %d\n", img->width*cols); 204 fprintf(fp, "HEIGHT %d\n", img->height*rows); 205 fprintf(fp, "DEPTH 4\n"); 206 fprintf(fp, "MAXVAL 255\n"); 207 fprintf(fp, "TUPLTYPE RGB_ALPHA\n"); 208 fprintf(fp, "ENDHDR\n"); 209 210 size_t line_size = img->width*sizeof(uint32_t); 211 for (int row=0; row<rows; ++row) { 212 for (int y=0; y<img->height; ++y) { 213 for (int col=0; col<cols; ++col) { 214 uint32_t offset=row*cols+col; 215 if (offset<img->frame_count) { 216 fwrite(img->frames[offset].rgba+y*line_size, sizeof(uint8_t), line_size, fp); 217 } 218 } 219 } 220 } 221 fclose(fp); 222 printf("Output: %s: %dx%dpx\n", fname, img->width*cols, img->height*rows); 223 return 0; 224 } 225 226 int write_ss(AnimatedImage *img, const char *fname, int cols) { 227 int rows = (int)img->frame_count/cols; 228 if ((int)img->frame_count%cols > 0) rows++; 229 size_t frame_size = img->width*img->height*sizeof(uint32_t); 230 size_t elem_size = img->width*sizeof(uint32_t); 231 size_t stride = elem_size*cols; 232 int width = img->width*cols; 233 int height = img->height*rows; 234 uint8_t *merged_orig = calloc(rows*cols, frame_size); 235 if (merged_orig==NULL) { 236 fputs("Memory allocation error", stderr); 237 return 1; 238 } 239 uint8_t *merged = merged_orig; 240 for (int row=0; row<rows; ++row) { 241 for (int y=0; y<img->height; ++y) { 242 for (int col=0; col<cols; ++col) { 243 uint32_t offset=row*cols+col; 244 if (offset<img->frame_count) { 245 memcpy(merged, img->frames[offset].rgba+y*elem_size, elem_size); 246 } 247 merged += elem_size; 248 } 249 } 250 } 251 printf("Output: %s: %dx%dpx\n", fname, width, height); 252 int ret = 1; 253 int fname_len = strlen(fname); 254 if (CHECK_EXT(fname, fname_len, ".webp", 5)) 255 ret = write_webp(fname, merged_orig, width, height, stride); 256 else if (CHECK_EXT(fname, fname_len, ".png", 4)) 257 ret = stbi_write_png(fname, width, height, 4, merged_orig, stride)==0; 258 else if (CHECK_EXT(fname, fname_len, ".bmp", 4)) 259 ret = stbi_write_bmp(fname, width, height, 4, merged_orig)==0; 260 else if (CHECK_EXT(fname, fname_len, ".tga", 4)) 261 ret = stbi_write_tga(fname, width, height, 4, merged_orig)==0; 262 else if (CHECK_EXT(fname, fname_len, ".jpg", 4)) 263 ret = stbi_write_jpg(fname, width, height, 4, merged_orig, 0)==0; 264 else 265 fputs("Unknown output format", stderr); 266 free(merged_orig); 267 return ret; 268 } 269 270 int save_file(AnimatedImage *img, char *out, int cols, int width) { 271 char out_gen[PATH_MAX] = {0}; 272 if (out==NULL) { 273 char *in_name = basename(img->path); 274 char *in_dir = dirname(img->path); 275 int n = snprintf(out_gen, PATH_MAX-1, "%s/atlas_%s", in_dir, in_name); 276 if (n<=0) { 277 fputs("Error generating output name", stderr); 278 return 1; 279 } 280 out = out_gen; 281 } 282 if (cols==0&&width==0) { 283 fputs("Either columns or width parameter has to be set", stderr); 284 return 1; 285 } else if (cols==0) { 286 cols = width/img->width; 287 } 288 if (cols==0) { 289 fputs("Invalid width", stderr); 290 return 1; 291 } 292 if (CHECK_EXT(out, strlen(out), ".pam", 4)) 293 return write_pam(img, out, cols); 294 else 295 return write_ss(img, out, cols); 296 } 297 298 void print_usage(int code) { 299 printf("Usage: %s [-w MAX_WIDTH_PIXELS] [-c COLUMNS] [-o OUTPUT_FILE] input_file.webp\n", argv0); 300 exit(code); 301 } 302 303 int main(int argc, char **argv) { 304 char *in_path = argv[argc-1]; 305 char *out_path = NULL; 306 int cols = 0; 307 int max_width = 0; 308 309 ARGBEGIN { 310 case 'o': 311 out_path = EARGF(print_usage(1)); 312 break; 313 case 'c': 314 cols = atoi(EARGF(print_usage(1))); 315 break; 316 case 'w': 317 max_width = atoi(EARGF(print_usage(1))); 318 break; 319 case 'h': 320 print_usage(0); 321 break; 322 default: 323 print_usage(1); 324 } ARGEND; 325 326 if (in_path==NULL||(cols<=0 && max_width<=0)) { 327 puts("invalid arguments"); 328 print_usage(1); 329 return 1; 330 } 331 AnimatedImage img = { .path=in_path }; 332 int ret = 0; 333 if (read_webp(in_path, &img)!=0) { 334 fputs("Error parsing WEBP", stderr); 335 ret = 1; 336 } 337 if (save_file(&img, out_path, cols, max_width)!=0) { 338 fputs("Error saving file", stderr); 339 ret = 1; 340 } 341 free_image(&img); 342 return ret; 343 }