blob: 0f95fc3438cfd9db2621bed8aea4fb7efded5b9c [file] [log] [blame]
// 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__GIF
#define WUFFS_CONFIG__MODULE__LZW
// 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 SRC_BUFFER_SIZE (64 * 1024)
// Global variable names start with a "g_" prefix.
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[SRC_BUFFER_SIZE] = {0};
wuffs_base__io_buffer g_src = {0};
wuffs_base__image_config g_image_config = {0};
wuffs_base__image_decoder* g_image_decoder = NULL;
union {
wuffs_gif__decoder gif;
} 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[0]) {
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) {
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 = 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 = calloc(n, 1);
if (!p) {
printf("%s: out of memory\n", g_filename);
return false;
}
g_pixbuf_slice.ptr = 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;
}
// This function always returns true. If we get this far, we still display a
// partial image, even if we encounter an error.
bool load_image_frame() {
while (true) {
wuffs_base__status status = wuffs_base__image_decoder__decode_frame(
g_image_decoder, &g_pixbuf, &g_src, WUFFS_BASE__PIXEL_BLEND__SRC,
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;
g_src.data.len = SRC_BUFFER_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);
switch (event->response_type & 0x7F) {
case XCB_EXPOSE: {
if (loaded) {
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;
}
loaded = load(c, s, w, g, argv[arg]);
xcb_clear_area(c, 1, w, 0, 0, 0xFFFF, 0xFFFF);
xcb_flush(c);
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);
}
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)