| // Copyright 2018 The Wuffs Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // ---------------- |
| |
| /* |
| gifplayer prints an ASCII representation of the GIF image read from stdin. To |
| play Eadweard Muybridge's iconic galloping horse animation, run: |
| |
| $CC gifplayer.c && ./a.out < ../../test/data/muybridge.gif; rm -f a.out |
| |
| for a C compiler $CC, such as clang or gcc. |
| |
| Add the -color flag to a.out to get 24 bit color ("true color") terminal output |
| (in the UTF-8 format) instead of plain ASCII output. Not all terminal emulators |
| support true color: https://gist.github.com/XVilka/8346728 |
| */ |
| |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| // ---------------- |
| |
| #if defined(__unix__) || defined(__MACH__) |
| |
| #include <time.h> |
| #include <unistd.h> |
| #define WUFFS_EXAMPLE_USE_TIMERS |
| |
| bool g_started = false; |
| struct timespec g_start_time = {0}; |
| |
| int64_t // |
| micros_since_start(struct timespec* now) { |
| if (!g_started) { |
| return 0; |
| } |
| int64_t nanos = (int64_t)(now->tv_sec - g_start_time.tv_sec) * 1000000000 + |
| (int64_t)(now->tv_nsec - g_start_time.tv_nsec); |
| if (nanos < 0) { |
| return 0; |
| } |
| return nanos / 1000; |
| } |
| |
| #else |
| #warning "TODO: timers on non-POSIX systems" |
| #endif |
| |
| // ---------------- |
| |
| // Wuffs ships as a "single file C library" or "header file library" as per |
| // https://github.com/nothings/stb/blob/master/docs/stb_howto.txt |
| // |
| // To use that single file as a "foo.c"-like implementation, instead of a |
| // "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or |
| // compiling it. |
| #define WUFFS_IMPLEMENTATION |
| |
| // Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of |
| // release/c/etc.c choose which parts of Wuffs to build. That file contains the |
| // entire Wuffs standard library, implementing a variety of codecs and file |
| // formats. Without this macro definition, an optimizing compiler or linker may |
| // very well discard Wuffs code for unused codecs, but listing the Wuffs |
| // modules we use makes that process explicit. Preprocessing means that such |
| // code simply isn't compiled. |
| #define WUFFS_CONFIG__MODULES |
| #define WUFFS_CONFIG__MODULE__BASE |
| #define WUFFS_CONFIG__MODULE__LZW |
| #define WUFFS_CONFIG__MODULE__GIF |
| |
| // If building this program in an environment that doesn't easily accommodate |
| // relative includes, you can use the script/inline-c-relative-includes.go |
| // program to generate a stand-alone C file. |
| #include "../../release/c/wuffs-unsupported-snapshot.c" |
| |
| #define TRY(error_msg) \ |
| do { \ |
| const char* z = error_msg; \ |
| if (z) { \ |
| return z; \ |
| } \ |
| } while (false) |
| |
| // Limit the input GIF image to (64 MiB - 1 byte) compressed and 4096 × 4096 |
| // pixels uncompressed. This is a limitation of this example program (which |
| // uses the Wuffs standard library), not a limitation of Wuffs per se. |
| // |
| // We keep the whole input in memory, instead of one-pass stream processing, |
| // because playing a looping animation requires re-winding the input. |
| #ifndef SRC_BUFFER_ARRAY_SIZE |
| #define SRC_BUFFER_ARRAY_SIZE (64 * 1024 * 1024) |
| #endif |
| #ifndef MAX_DIMENSION |
| #define MAX_DIMENSION (4096) |
| #endif |
| |
| uint8_t g_src_buffer_array[SRC_BUFFER_ARRAY_SIZE] = {0}; |
| size_t g_src_len = 0; |
| |
| uint8_t* g_curr_dst_buffer = NULL; |
| uint8_t* g_prev_dst_buffer = NULL; |
| size_t g_dst_len; // Length in bytes. |
| |
| wuffs_base__slice_u8 g_workbuf = {0}; |
| wuffs_base__slice_u8 g_printbuf = {0}; |
| |
| bool g_first_play = true; |
| uint32_t g_num_loops_remaining = 0; |
| wuffs_base__image_config g_ic = {0}; |
| wuffs_base__pixel_buffer g_pb = {0}; |
| |
| wuffs_base__flicks g_cumulative_delay_micros = 0; |
| |
| const char* // |
| read_stdin() { |
| while (g_src_len < SRC_BUFFER_ARRAY_SIZE) { |
| size_t n = fread(g_src_buffer_array + g_src_len, sizeof(uint8_t), |
| SRC_BUFFER_ARRAY_SIZE - g_src_len, stdin); |
| g_src_len += n; |
| if (feof(stdin)) { |
| return NULL; |
| } else if (ferror(stdin)) { |
| return "read error"; |
| } |
| } |
| return "input is too large"; |
| } |
| |
| // ---- |
| |
| struct { |
| int remaining_argc; |
| char** remaining_argv; |
| |
| bool color; |
| bool quirk_honor_background_color; |
| } g_flags = {0}; |
| |
| const char* // |
| parse_flags(int argc, char** argv) { |
| int c = (argc > 0) ? 1 : 0; // Skip argv[0], the program name. |
| for (; c < argc; c++) { |
| char* arg = argv[c]; |
| if (*arg++ != '-') { |
| break; |
| } |
| |
| // A double-dash "--foo" is equivalent to a single-dash "-foo". As special |
| // cases, a bare "-" is not a flag (some programs may interpret it as |
| // stdin) and a bare "--" means to stop parsing flags. |
| if (*arg == '\x00') { |
| break; |
| } else if (*arg == '-') { |
| arg++; |
| if (*arg == '\x00') { |
| c++; |
| break; |
| } |
| } |
| |
| if (!strcmp(arg, "c") || !strcmp(arg, "color")) { |
| g_flags.color = true; |
| continue; |
| } |
| if (!strcmp(arg, "quirk_honor_background_color")) { |
| g_flags.quirk_honor_background_color = true; |
| continue; |
| } |
| |
| return "main: unrecognized flag argument"; |
| } |
| |
| g_flags.remaining_argc = argc - c; |
| g_flags.remaining_argv = argv + c; |
| return NULL; |
| } |
| |
| // ---- |
| |
| // BYTES_PER_COLOR_PIXEL is long enough to contain "\x1B[38;2;255;255;255mABC" |
| // plus a trailing NUL byte and a few bytes of slack. It starts with a true |
| // color terminal escape code. ABC is three bytes for the UTF-8 representation |
| // "\xE2\x96\x88" of "█", U+2588 FULL BLOCK. |
| #define BYTES_PER_COLOR_PIXEL 32 |
| |
| const char* g_reset_color = "\x1B[0m"; |
| |
| void // |
| restore_background(wuffs_base__pixel_buffer* pb, |
| wuffs_base__rect_ie_u32 bounds, |
| wuffs_base__color_u32_argb_premul background_color) { |
| size_t width4 = (size_t)(wuffs_base__pixel_config__width(&pb->pixcfg)) * 4; |
| size_t y; |
| for (y = bounds.min_incl_y; y < bounds.max_excl_y; y++) { |
| size_t x; |
| uint8_t* d = g_curr_dst_buffer + (y * width4) + (bounds.min_incl_x * 4); |
| for (x = bounds.min_incl_x; x < bounds.max_excl_x; x++) { |
| wuffs_base__poke_u32le__no_bounds_check(d, background_color); |
| d += sizeof(wuffs_base__color_u32_argb_premul); |
| } |
| } |
| } |
| |
| size_t // |
| print_ascii_art(wuffs_base__pixel_buffer* pb) { |
| uint32_t width = wuffs_base__pixel_config__width(&pb->pixcfg); |
| uint32_t height = wuffs_base__pixel_config__height(&pb->pixcfg); |
| |
| uint8_t* d = g_curr_dst_buffer; |
| uint8_t* p = g_printbuf.ptr; |
| *p++ = '\n'; |
| uint32_t y; |
| for (y = 0; y < height; y++) { |
| uint32_t x; |
| for (x = 0; x < width; x++) { |
| wuffs_base__color_u32_argb_premul c = |
| wuffs_base__peek_u32le__no_bounds_check(d); |
| d += sizeof(wuffs_base__color_u32_argb_premul); |
| // Convert to grayscale via the formula |
| // Y = (0.299 * R) + (0.587 * G) + (0.114 * B) |
| // translated into fixed point arithmetic. |
| uint32_t b = 0xFF & (c >> 0); |
| uint32_t g = 0xFF & (c >> 8); |
| uint32_t r = 0xFF & (c >> 16); |
| uint32_t gray = |
| ((19595 * r) + (38470 * g) + (7471 * b) + (1 << 15)) >> 16; |
| *p++ = "-:=+IOX@"[(gray & 0xFF) >> 5]; |
| } |
| *p++ = '\n'; |
| } |
| return p - g_printbuf.ptr; |
| } |
| |
| size_t // |
| print_color_art(wuffs_base__pixel_buffer* pb) { |
| uint32_t width = wuffs_base__pixel_config__width(&pb->pixcfg); |
| uint32_t height = wuffs_base__pixel_config__height(&pb->pixcfg); |
| |
| uint8_t* d = g_curr_dst_buffer; |
| uint8_t* p = g_printbuf.ptr; |
| *p++ = '\n'; |
| p += sprintf((char*)p, "%s", g_reset_color); |
| uint32_t y; |
| for (y = 0; y < height; y++) { |
| uint32_t x; |
| for (x = 0; x < width; x++) { |
| wuffs_base__color_u32_argb_premul c = |
| wuffs_base__peek_u32le__no_bounds_check(d); |
| d += sizeof(wuffs_base__color_u32_argb_premul); |
| int b = 0xFF & (c >> 0); |
| int g = 0xFF & (c >> 8); |
| int r = 0xFF & (c >> 16); |
| // "\xE2\x96\x88" is U+2588 FULL BLOCK. Before that is a true color |
| // terminal escape code. |
| p += sprintf((char*)p, "\x1B[38;2;%d;%d;%dm\xE2\x96\x88", r, g, b); |
| } |
| *p++ = '\n'; |
| } |
| p += sprintf((char*)p, "%s", g_reset_color); |
| return p - g_printbuf.ptr; |
| } |
| |
| // ---- |
| |
| const char* // |
| try_allocate(wuffs_gif__decoder* dec) { |
| uint32_t width = wuffs_base__pixel_config__width(&g_ic.pixcfg); |
| uint32_t height = wuffs_base__pixel_config__height(&g_ic.pixcfg); |
| uint64_t num_pixels = ((uint64_t)width) * ((uint64_t)height); |
| if (num_pixels > (SIZE_MAX / sizeof(wuffs_base__color_u32_argb_premul))) { |
| return "could not allocate dst buffer"; |
| } |
| |
| g_dst_len = num_pixels * sizeof(wuffs_base__color_u32_argb_premul); |
| g_curr_dst_buffer = (uint8_t*)calloc(g_dst_len, 1); |
| if (!g_curr_dst_buffer) { |
| return "could not allocate curr-dst buffer"; |
| } |
| |
| g_prev_dst_buffer = (uint8_t*)malloc(g_dst_len); |
| if (!g_prev_dst_buffer) { |
| return "could not allocate prev-dst buffer"; |
| } |
| |
| uint64_t workbuf_len_max_incl = wuffs_gif__decoder__workbuf_len(dec).max_incl; |
| if (workbuf_len_max_incl > 0) { |
| g_workbuf = wuffs_base__malloc_slice_u8( |
| malloc, wuffs_gif__decoder__workbuf_len(dec).max_incl); |
| if (!g_workbuf.ptr) { |
| return "could not allocate work buffer"; |
| } |
| } else { |
| g_workbuf = wuffs_base__make_slice_u8(NULL, 0); |
| } |
| |
| uint64_t plen = 1 + ((uint64_t)(width) + 1) * (uint64_t)(height); |
| uint64_t bytes_per_print_pixel = g_flags.color ? BYTES_PER_COLOR_PIXEL : 1; |
| if (plen <= ((uint64_t)SIZE_MAX) / bytes_per_print_pixel) { |
| g_printbuf = |
| wuffs_base__malloc_slice_u8(malloc, plen * bytes_per_print_pixel); |
| } |
| if (!g_printbuf.ptr) { |
| return "could not allocate print buffer"; |
| } |
| |
| return NULL; |
| } |
| |
| const char* // |
| allocate(wuffs_gif__decoder* dec) { |
| const char* status_msg = try_allocate(dec); |
| if (status_msg) { |
| free(g_printbuf.ptr); |
| g_printbuf = wuffs_base__make_slice_u8(NULL, 0); |
| free(g_workbuf.ptr); |
| g_workbuf = wuffs_base__make_slice_u8(NULL, 0); |
| free(g_prev_dst_buffer); |
| g_prev_dst_buffer = NULL; |
| free(g_curr_dst_buffer); |
| g_curr_dst_buffer = NULL; |
| g_dst_len = 0; |
| } |
| return status_msg; |
| } |
| |
| const char* // |
| play() { |
| wuffs_gif__decoder dec; |
| wuffs_base__status i_status = |
| wuffs_gif__decoder__initialize(&dec, sizeof dec, WUFFS_VERSION, 0); |
| if (!wuffs_base__status__is_ok(&i_status)) { |
| return wuffs_base__status__message(&i_status); |
| } |
| |
| if (g_flags.quirk_honor_background_color) { |
| wuffs_gif__decoder__set_quirk_enabled( |
| &dec, WUFFS_GIF__QUIRK_HONOR_BACKGROUND_COLOR, true); |
| } |
| |
| wuffs_base__io_buffer src; |
| src.data.ptr = &g_src_buffer_array[0]; |
| src.data.len = g_src_len; |
| src.meta.wi = g_src_len; |
| src.meta.ri = 0; |
| src.meta.pos = 0; |
| src.meta.closed = true; |
| |
| if (g_first_play) { |
| wuffs_base__status dic_status = |
| wuffs_gif__decoder__decode_image_config(&dec, &g_ic, &src); |
| if (!wuffs_base__status__is_ok(&dic_status)) { |
| return wuffs_base__status__message(&dic_status); |
| } |
| if (!wuffs_base__image_config__is_valid(&g_ic)) { |
| return "invalid image configuration"; |
| } |
| uint32_t width = wuffs_base__pixel_config__width(&g_ic.pixcfg); |
| uint32_t height = wuffs_base__pixel_config__height(&g_ic.pixcfg); |
| if ((width > MAX_DIMENSION) || (height > MAX_DIMENSION)) { |
| return "image dimensions are too large"; |
| } |
| |
| // Override the source's indexed pixel format to be non-indexed. |
| wuffs_base__pixel_config__set( |
| &g_ic.pixcfg, WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL, |
| WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, width, height); |
| |
| const char* msg = allocate(&dec); |
| if (msg) { |
| return msg; |
| } |
| wuffs_base__status sfs0_status = wuffs_base__pixel_buffer__set_from_slice( |
| &g_pb, &g_ic.pixcfg, |
| wuffs_base__make_slice_u8(g_curr_dst_buffer, g_dst_len)); |
| if (!wuffs_base__status__is_ok(&sfs0_status)) { |
| return wuffs_base__status__message(&sfs0_status); |
| } |
| } |
| |
| while (1) { |
| wuffs_base__frame_config fc = {0}; |
| wuffs_base__status dfc_status = |
| wuffs_gif__decoder__decode_frame_config(&dec, &fc, &src); |
| if (!wuffs_base__status__is_ok(&dfc_status)) { |
| if (dfc_status.repr == wuffs_base__note__end_of_data) { |
| break; |
| } |
| return wuffs_base__status__message(&dfc_status); |
| } |
| |
| if (wuffs_base__frame_config__index(&fc) == 0) { |
| wuffs_base__color_u32_argb_premul background_color = |
| wuffs_base__frame_config__background_color(&fc); |
| size_t i; |
| size_t n = g_dst_len / sizeof(wuffs_base__color_u32_argb_premul); |
| uint8_t* p = g_curr_dst_buffer; |
| for (i = 0; i < n; i++) { |
| wuffs_base__poke_u32le__no_bounds_check(p, background_color); |
| p += sizeof(wuffs_base__color_u32_argb_premul); |
| } |
| } |
| |
| switch (wuffs_base__frame_config__disposal(&fc)) { |
| case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS: { |
| memcpy(g_prev_dst_buffer, g_curr_dst_buffer, g_dst_len); |
| break; |
| } |
| } |
| |
| wuffs_base__status decode_frame_status = wuffs_gif__decoder__decode_frame( |
| &dec, &g_pb, &src, |
| wuffs_base__frame_config__overwrite_instead_of_blend(&fc) |
| ? WUFFS_BASE__PIXEL_BLEND__SRC |
| : WUFFS_BASE__PIXEL_BLEND__SRC_OVER, |
| g_workbuf, NULL); |
| if (decode_frame_status.repr == wuffs_base__note__end_of_data) { |
| break; |
| } |
| |
| size_t n = g_flags.color ? print_color_art(&g_pb) : print_ascii_art(&g_pb); |
| |
| switch (wuffs_base__frame_config__disposal(&fc)) { |
| case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND: { |
| restore_background(&g_pb, wuffs_base__frame_config__bounds(&fc), |
| wuffs_base__frame_config__background_color(&fc)); |
| break; |
| } |
| case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS: { |
| uint8_t* swap = g_curr_dst_buffer; |
| g_curr_dst_buffer = g_prev_dst_buffer; |
| g_prev_dst_buffer = swap; |
| |
| wuffs_base__status sfs1_status = |
| wuffs_base__pixel_buffer__set_from_slice( |
| &g_pb, &g_ic.pixcfg, |
| wuffs_base__make_slice_u8(g_curr_dst_buffer, g_dst_len)); |
| if (!wuffs_base__status__is_ok(&sfs1_status)) { |
| return wuffs_base__status__message(&sfs1_status); |
| } |
| break; |
| } |
| } |
| |
| #if defined(WUFFS_EXAMPLE_USE_TIMERS) |
| if (g_started) { |
| struct timespec now; |
| if (clock_gettime(CLOCK_MONOTONIC, &now)) { |
| return strerror(errno); |
| } |
| int64_t elapsed_micros = micros_since_start(&now); |
| if (g_cumulative_delay_micros > elapsed_micros) { |
| usleep(g_cumulative_delay_micros - elapsed_micros); |
| } |
| |
| } else { |
| if (clock_gettime(CLOCK_MONOTONIC, &g_start_time)) { |
| return strerror(errno); |
| } |
| g_started = true; |
| } |
| #endif |
| |
| fwrite(g_printbuf.ptr, sizeof(uint8_t), n, stdout); |
| fflush(stdout); |
| |
| g_cumulative_delay_micros += |
| (1000 * wuffs_base__frame_config__duration(&fc)) / |
| WUFFS_BASE__FLICKS_PER_MILLISECOND; |
| |
| // TODO: should a zero duration mean to show this frame forever? |
| |
| if (!wuffs_base__status__is_ok(&decode_frame_status)) { |
| return wuffs_base__status__message(&decode_frame_status); |
| } |
| } |
| |
| if (g_first_play) { |
| g_first_play = false; |
| g_num_loops_remaining = wuffs_gif__decoder__num_animation_loops(&dec); |
| } |
| |
| return NULL; |
| } |
| |
| const char* // |
| main1(int argc, char** argv) { |
| TRY(parse_flags(argc, argv)); |
| if (g_flags.remaining_argc > 0) { |
| return "main: bad argument: use \"program < input\", not \"program input\""; |
| } |
| TRY(read_stdin()); |
| while (true) { |
| TRY(play()); |
| if (g_num_loops_remaining == 0) { |
| continue; |
| } |
| g_num_loops_remaining--; |
| if (g_num_loops_remaining == 0) { |
| break; |
| } |
| } |
| return NULL; |
| } |
| |
| int // |
| main(int argc, char** argv) { |
| const char* z = main1(argc, argv); |
| if (z) { |
| fprintf(stderr, "%s\n", z); |
| return 1; |
| } |
| return 0; |
| } |