| // Copyright 2020 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. |
| |
| // ---------------- |
| |
| /* |
| imageviewer is a simple GUI program for viewing images. On Linux, GUI means |
| X11. To run: |
| |
| $CC imageviewer.c -lxcb -lxcb-image && \ |
| ./a.out ../../test/data/bricks-*.gif; rm -f a.out |
| |
| for a C compiler $CC, such as clang or gcc. |
| |
| The Space and BackSpace keys cycle through the files, if more than one was |
| given as command line arguments. If none were given, the program reads from |
| stdin. |
| |
| The Return key is equivalent to the Space key. |
| |
| The Escape key quits. |
| */ |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| // 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 whitelist 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__BMP |
| #define WUFFS_CONFIG__MODULE__GIF |
| #define WUFFS_CONFIG__MODULE__LZW |
| #define WUFFS_CONFIG__MODULE__WBMP |
| |
| // 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" |
| |
| // X11 limits its image dimensions to uint16_t. |
| #define MAX_DIMENSION 65535 |
| |
| #define NUM_BACKGROUND_COLORS 3 |
| #define SRC_BUFFER_ARRAY_SIZE (64 * 1024) |
| |
| wuffs_base__color_u32_argb_premul g_background_colors[NUM_BACKGROUND_COLORS] = { |
| 0xFF000000, |
| 0xFFFFFFFF, |
| 0xFFA9009A, |
| }; |
| |
| FILE* g_file = NULL; |
| const char* g_filename = NULL; |
| uint32_t g_width = 0; |
| uint32_t g_height = 0; |
| wuffs_base__slice_u8 g_workbuf_slice = {0}; |
| wuffs_base__slice_u8 g_pixbuf_slice = {0}; |
| wuffs_base__pixel_buffer g_pixbuf = {0}; |
| uint8_t g_src_buffer_array[SRC_BUFFER_ARRAY_SIZE] = {0}; |
| wuffs_base__io_buffer g_src = {0}; |
| wuffs_base__image_config g_image_config = {0}; |
| wuffs_base__frame_config g_frame_config = {0}; |
| wuffs_base__image_decoder* g_image_decoder = NULL; |
| uint32_t g_background_color_index = 0; |
| |
| union { |
| wuffs_bmp__decoder bmp; |
| wuffs_gif__decoder gif; |
| wuffs_wbmp__decoder wbmp; |
| } g_potential_decoders; |
| |
| bool // |
| read_more_src() { |
| if (g_src.meta.closed) { |
| printf("%s: unexpected end of file\n", g_filename); |
| return false; |
| } |
| wuffs_base__io_buffer__compact(&g_src); |
| g_src.meta.wi += fread(g_src.data.ptr + g_src.meta.wi, sizeof(uint8_t), |
| g_src.data.len - g_src.meta.wi, g_file); |
| if (feof(g_file)) { |
| g_src.meta.closed = true; |
| } else if (ferror(g_file)) { |
| printf("%s: read error\n", g_filename); |
| return false; |
| } |
| return true; |
| } |
| |
| bool // |
| load_image_type() { |
| while (g_src.meta.wi == 0) { |
| if (!read_more_src()) { |
| return false; |
| } |
| } |
| |
| wuffs_base__status status; |
| switch (g_src_buffer_array[0]) { |
| case '\x00': |
| status = wuffs_wbmp__decoder__initialize( |
| &g_potential_decoders.wbmp, sizeof g_potential_decoders.wbmp, |
| WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS); |
| if (!wuffs_base__status__is_ok(&status)) { |
| printf("%s: %s\n", g_filename, wuffs_base__status__message(&status)); |
| return false; |
| } |
| g_image_decoder = |
| wuffs_wbmp__decoder__upcast_as__wuffs_base__image_decoder( |
| &g_potential_decoders.wbmp); |
| break; |
| |
| case 'B': |
| status = wuffs_bmp__decoder__initialize( |
| &g_potential_decoders.bmp, sizeof g_potential_decoders.bmp, |
| WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS); |
| if (!wuffs_base__status__is_ok(&status)) { |
| printf("%s: %s\n", g_filename, wuffs_base__status__message(&status)); |
| return false; |
| } |
| g_image_decoder = |
| wuffs_bmp__decoder__upcast_as__wuffs_base__image_decoder( |
| &g_potential_decoders.bmp); |
| break; |
| |
| case 'G': |
| status = wuffs_gif__decoder__initialize( |
| &g_potential_decoders.gif, sizeof g_potential_decoders.gif, |
| WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS); |
| if (!wuffs_base__status__is_ok(&status)) { |
| printf("%s: %s\n", g_filename, wuffs_base__status__message(&status)); |
| return false; |
| } |
| g_image_decoder = |
| wuffs_gif__decoder__upcast_as__wuffs_base__image_decoder( |
| &g_potential_decoders.gif); |
| break; |
| |
| default: |
| printf("%s: unrecognized file format\n", g_filename); |
| return false; |
| } |
| return true; |
| } |
| |
| bool // |
| load_image_config() { |
| // Decode the wuffs_base__image_config. |
| while (true) { |
| wuffs_base__status status = wuffs_base__image_decoder__decode_image_config( |
| g_image_decoder, &g_image_config, &g_src); |
| |
| if (status.repr == NULL) { |
| break; |
| } else if (status.repr != wuffs_base__suspension__short_read) { |
| // TODO: handle wuffs_base__note__i_o_redirect. |
| printf("%s: %s\n", g_filename, wuffs_base__status__message(&status)); |
| return false; |
| } |
| |
| if (!read_more_src()) { |
| return false; |
| } |
| } |
| |
| // Read the dimensions. |
| uint32_t w = wuffs_base__pixel_config__width(&g_image_config.pixcfg); |
| uint32_t h = wuffs_base__pixel_config__height(&g_image_config.pixcfg); |
| if ((w > MAX_DIMENSION) || (h > MAX_DIMENSION)) { |
| printf("%s: image is too large\n", g_filename); |
| return false; |
| } |
| g_width = w; |
| g_height = h; |
| |
| // Override the image's native pixel format to be BGRA_PREMUL. |
| wuffs_base__pixel_config__set(&g_image_config.pixcfg, |
| WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL, |
| WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, w, h); |
| |
| // Allocate the work buffer memory. |
| uint64_t workbuf_len = |
| wuffs_base__image_decoder__workbuf_len(g_image_decoder).max_incl; |
| if (workbuf_len > SIZE_MAX) { |
| printf("%s: out of memory\n", g_filename); |
| return false; |
| } |
| if (workbuf_len > 0) { |
| void* p = malloc(workbuf_len); |
| if (!p) { |
| printf("%s: out of memory\n", g_filename); |
| return false; |
| } |
| g_workbuf_slice.ptr = (uint8_t*)p; |
| g_workbuf_slice.len = workbuf_len; |
| } |
| |
| // Allocate the pixel buffer memory. |
| uint64_t num_pixels = ((uint64_t)w) * ((uint64_t)h); |
| if (num_pixels > (SIZE_MAX / sizeof(wuffs_base__color_u32_argb_premul))) { |
| printf("%s: image is too large\n", g_filename); |
| return false; |
| } |
| size_t n = num_pixels * sizeof(wuffs_base__color_u32_argb_premul); |
| void* p = malloc(n); |
| if (!p) { |
| printf("%s: out of memory\n", g_filename); |
| return false; |
| } |
| { |
| uint8_t* ptr = (uint8_t*)p; |
| wuffs_base__color_u32_argb_premul color = |
| g_background_colors[g_background_color_index]; |
| for (size_t i = 0; i < num_pixels; i++) { |
| wuffs_base__store_u32le__no_bounds_check(ptr, color); |
| ptr += 4; |
| } |
| } |
| g_pixbuf_slice.ptr = (uint8_t*)p; |
| g_pixbuf_slice.len = n; |
| |
| // Configure the wuffs_base__pixel_buffer struct. |
| wuffs_base__status status = wuffs_base__pixel_buffer__set_from_slice( |
| &g_pixbuf, &g_image_config.pixcfg, g_pixbuf_slice); |
| if (!wuffs_base__status__is_ok(&status)) { |
| printf("%s: %s\n", g_filename, wuffs_base__status__message(&status)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool // |
| load_image_frame() { |
| // Decode the wuffs_base__frame_config. |
| while (true) { |
| wuffs_base__status status = wuffs_base__image_decoder__decode_frame_config( |
| g_image_decoder, &g_frame_config, &g_src); |
| |
| if (status.repr == NULL) { |
| break; |
| } else if (status.repr != wuffs_base__suspension__short_read) { |
| printf("%s: %s\n", g_filename, wuffs_base__status__message(&status)); |
| return false; |
| } |
| |
| if (!read_more_src()) { |
| return false; |
| } |
| } |
| |
| // From here on, this function always returns true. If we get this far, we |
| // still display a partial image, even if we encounter an error. |
| |
| // Decode the frame (the pixels). |
| while (true) { |
| wuffs_base__status status = wuffs_base__image_decoder__decode_frame( |
| g_image_decoder, &g_pixbuf, &g_src, |
| wuffs_base__frame_config__overwrite_instead_of_blend(&g_frame_config) |
| ? WUFFS_BASE__PIXEL_BLEND__SRC |
| : WUFFS_BASE__PIXEL_BLEND__SRC_OVER, |
| g_workbuf_slice, NULL); |
| |
| if (status.repr == NULL) { |
| break; |
| } else if (status.repr != wuffs_base__suspension__short_read) { |
| printf("%s: %s\n", g_filename, wuffs_base__status__message(&status)); |
| return true; |
| } |
| |
| if (!read_more_src()) { |
| return true; |
| } |
| } |
| |
| uint32_t w = wuffs_base__pixel_config__width(&g_image_config.pixcfg); |
| uint32_t h = wuffs_base__pixel_config__height(&g_image_config.pixcfg); |
| printf("%s: ok (%" PRIu32 " x %" PRIu32 ")\n", g_filename, w, h); |
| return true; |
| } |
| |
| bool // |
| load_image(const char* filename) { |
| if (g_workbuf_slice.ptr != NULL) { |
| free(g_workbuf_slice.ptr); |
| g_workbuf_slice.ptr = NULL; |
| g_workbuf_slice.len = 0; |
| } |
| if (g_pixbuf_slice.ptr != NULL) { |
| free(g_pixbuf_slice.ptr); |
| g_pixbuf_slice.ptr = NULL; |
| g_pixbuf_slice.len = 0; |
| } |
| g_width = 0; |
| g_height = 0; |
| g_src.data.ptr = g_src_buffer_array; |
| g_src.data.len = SRC_BUFFER_ARRAY_SIZE; |
| g_src.meta.wi = 0; |
| g_src.meta.ri = 0; |
| g_src.meta.pos = 0; |
| g_src.meta.closed = false; |
| g_image_config = wuffs_base__null_image_config(); |
| g_image_decoder = NULL; |
| |
| g_file = stdin; |
| g_filename = "<stdin>"; |
| if (filename) { |
| FILE* f = fopen(filename, "r"); |
| if (f == NULL) { |
| printf("%s: could not open file\n", filename); |
| return false; |
| } |
| g_file = f; |
| g_filename = filename; |
| } |
| |
| bool ret = load_image_type() && load_image_config() && load_image_frame(); |
| if (filename) { |
| fclose(g_file); |
| g_file = NULL; |
| } |
| return ret; |
| } |
| |
| // --------------------------------------------------------------------- |
| |
| #if defined(__linux__) |
| #define SUPPORTED_OPERATING_SYSTEM |
| |
| #include <xcb/xcb.h> |
| #include <xcb/xcb_image.h> |
| |
| #define XK_BackSpace 0xFF08 |
| #define XK_Escape 0xFF1B |
| #define XK_Return 0xFF0D |
| |
| xcb_atom_t g_atom_net_wm_name = XCB_NONE; |
| xcb_atom_t g_atom_utf8_string = XCB_NONE; |
| xcb_atom_t g_atom_wm_protocols = XCB_NONE; |
| xcb_atom_t g_atom_wm_delete_window = XCB_NONE; |
| xcb_pixmap_t g_pixmap = XCB_NONE; |
| xcb_keysym_t* g_keysyms = NULL; |
| xcb_get_keyboard_mapping_reply_t* g_keyboard_mapping = NULL; |
| |
| void // |
| init_keymap(xcb_connection_t* c, const xcb_setup_t* z) { |
| xcb_get_keyboard_mapping_cookie_t cookie = xcb_get_keyboard_mapping( |
| c, z->min_keycode, z->max_keycode - z->min_keycode + 1); |
| g_keyboard_mapping = xcb_get_keyboard_mapping_reply(c, cookie, NULL); |
| g_keysyms = (xcb_keysym_t*)(g_keyboard_mapping + 1); |
| } |
| |
| xcb_window_t // |
| make_window(xcb_connection_t* c, xcb_screen_t* s) { |
| xcb_window_t w = xcb_generate_id(c); |
| uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; |
| uint32_t value_list[2]; |
| value_list[0] = s->black_pixel; |
| value_list[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS; |
| xcb_create_window(c, 0, w, s->root, 0, 0, 1024, 768, 0, |
| XCB_WINDOW_CLASS_INPUT_OUTPUT, s->root_visual, value_mask, |
| value_list); |
| xcb_change_property(c, XCB_PROP_MODE_REPLACE, w, g_atom_net_wm_name, |
| g_atom_utf8_string, 8, 12, "Image Viewer"); |
| xcb_change_property(c, XCB_PROP_MODE_REPLACE, w, g_atom_wm_protocols, |
| XCB_ATOM_ATOM, 32, 1, &g_atom_wm_delete_window); |
| xcb_map_window(c, w); |
| return w; |
| } |
| |
| bool // |
| load(xcb_connection_t* c, |
| xcb_screen_t* s, |
| xcb_window_t w, |
| xcb_gcontext_t g, |
| const char* filename) { |
| if (g_pixmap != XCB_NONE) { |
| xcb_free_pixmap(c, g_pixmap); |
| } |
| |
| if (!load_image(filename)) { |
| return false; |
| } |
| |
| xcb_create_pixmap(c, s->root_depth, g_pixmap, w, g_width, g_height); |
| xcb_image_t* image = xcb_image_create_native( |
| c, g_width, g_height, XCB_IMAGE_FORMAT_Z_PIXMAP, s->root_depth, NULL, |
| g_pixbuf_slice.len, g_pixbuf_slice.ptr); |
| xcb_image_put(c, g_pixmap, g, image, 0, 0, 0); |
| xcb_image_destroy(image); |
| return true; |
| } |
| |
| int // |
| main(int argc, char** argv) { |
| xcb_connection_t* c = xcb_connect(NULL, NULL); |
| const xcb_setup_t* z = xcb_get_setup(c); |
| xcb_screen_t* s = xcb_setup_roots_iterator(z).data; |
| |
| { |
| xcb_intern_atom_cookie_t cookie0 = |
| xcb_intern_atom(c, 1, 12, "_NET_WM_NAME"); |
| xcb_intern_atom_cookie_t cookie1 = xcb_intern_atom(c, 1, 11, "UTF8_STRING"); |
| xcb_intern_atom_cookie_t cookie2 = |
| xcb_intern_atom(c, 1, 12, "WM_PROTOCOLS"); |
| xcb_intern_atom_cookie_t cookie3 = |
| xcb_intern_atom(c, 1, 16, "WM_DELETE_WINDOW"); |
| xcb_intern_atom_reply_t* reply0 = xcb_intern_atom_reply(c, cookie0, NULL); |
| xcb_intern_atom_reply_t* reply1 = xcb_intern_atom_reply(c, cookie1, NULL); |
| xcb_intern_atom_reply_t* reply2 = xcb_intern_atom_reply(c, cookie2, NULL); |
| xcb_intern_atom_reply_t* reply3 = xcb_intern_atom_reply(c, cookie3, NULL); |
| g_atom_net_wm_name = reply0->atom; |
| g_atom_utf8_string = reply1->atom; |
| g_atom_wm_protocols = reply2->atom; |
| g_atom_wm_delete_window = reply3->atom; |
| free(reply0); |
| free(reply1); |
| free(reply2); |
| free(reply3); |
| } |
| |
| xcb_window_t w = make_window(c, s); |
| xcb_gcontext_t g = xcb_generate_id(c); |
| xcb_create_gc(c, g, w, 0, NULL); |
| init_keymap(c, z); |
| xcb_flush(c); |
| g_pixmap = xcb_generate_id(c); |
| |
| bool loaded = load(c, s, w, g, (argc > 1) ? argv[1] : NULL); |
| int arg = 1; |
| |
| while (true) { |
| xcb_generic_event_t* event = xcb_wait_for_event(c); |
| bool reload = false; |
| |
| switch (event->response_type & 0x7F) { |
| case XCB_EXPOSE: { |
| xcb_expose_event_t* e = (xcb_expose_event_t*)event; |
| if (loaded && (e->count == 0)) { |
| xcb_copy_area(c, g_pixmap, w, g, 0, 0, 0, 0, g_width, g_height); |
| xcb_flush(c); |
| } |
| break; |
| } |
| |
| case XCB_KEY_PRESS: { |
| xcb_key_press_event_t* e = (xcb_key_press_event_t*)event; |
| uint32_t i = e->detail; |
| if ((z->min_keycode <= i) && (i <= z->max_keycode)) { |
| i = g_keysyms[(i - z->min_keycode) * |
| g_keyboard_mapping->keysyms_per_keycode]; |
| switch (i) { |
| case XK_Escape: |
| return 0; |
| |
| case ' ': |
| case XK_BackSpace: |
| case XK_Return: |
| if (argc <= 2) { |
| break; |
| } |
| arg += (i != XK_BackSpace) ? +1 : -1; |
| if (arg == 0) { |
| arg = argc - 1; |
| } else if (arg == argc) { |
| arg = 1; |
| } |
| reload = true; |
| break; |
| |
| case ',': |
| case '.': |
| g_background_color_index += |
| (i == ',') ? (NUM_BACKGROUND_COLORS - 1) : 1; |
| g_background_color_index %= NUM_BACKGROUND_COLORS; |
| reload = true; |
| break; |
| } |
| } |
| break; |
| } |
| |
| case XCB_CLIENT_MESSAGE: { |
| xcb_client_message_event_t* e = (xcb_client_message_event_t*)event; |
| if (e->data.data32[0] == g_atom_wm_delete_window) { |
| return 0; |
| } |
| break; |
| } |
| } |
| |
| free(event); |
| |
| if (reload) { |
| loaded = load(c, s, w, g, argv[arg]); |
| xcb_clear_area(c, 1, w, 0, 0, 0xFFFF, 0xFFFF); |
| xcb_flush(c); |
| } |
| } |
| return 0; |
| } |
| |
| #endif // defined(__linux__) |
| |
| // --------------------------------------------------------------------- |
| |
| #if !defined(SUPPORTED_OPERATING_SYSTEM) |
| |
| int // |
| main(int argc, char** argv) { |
| printf("unsupported operating system\n"); |
| return 1; |
| } |
| |
| #endif // !defined(SUPPORTED_OPERATING_SYSTEM) |