blob: 3b12f89d4cc50e94b7243cd4f5b4786f74ed25ed [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:
$CXX imageviewer.cc -lxcb -lxcb-image && \
./a.out ../../test/data/bricks-*.gif; rm -f a.out
for a C++ compiler $CXX, such as clang++ or g++.
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 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__ADLER32
#define WUFFS_CONFIG__MODULE__AUX__BASE
#define WUFFS_CONFIG__MODULE__AUX__IMAGE
#define WUFFS_CONFIG__MODULE__BASE
#define WUFFS_CONFIG__MODULE__BMP
#define WUFFS_CONFIG__MODULE__CRC32
#define WUFFS_CONFIG__MODULE__DEFLATE
#define WUFFS_CONFIG__MODULE__GIF
#define WUFFS_CONFIG__MODULE__LZW
#define WUFFS_CONFIG__MODULE__NIE
#define WUFFS_CONFIG__MODULE__PNG
#define WUFFS_CONFIG__MODULE__WBMP
#define WUFFS_CONFIG__MODULE__ZLIB
// 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_INCL_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,
};
uint32_t g_width = 0;
uint32_t g_height = 0;
wuffs_aux::MemOwner g_pixbuf_mem_owner(nullptr, &free);
wuffs_base__slice_u8 g_pixbuf_mem_slice = {0};
uint32_t g_background_color_index = 0;
bool //
load_image(const char* filename) {
FILE* file = stdin;
const char* adj_filename = "<stdin>";
if (filename) {
FILE* f = fopen(filename, "r");
if (f == NULL) {
printf("%s: could not open file\n", filename);
return false;
}
file = f;
adj_filename = filename;
}
g_width = 0;
g_height = 0;
g_pixbuf_mem_owner.reset();
g_pixbuf_mem_slice = wuffs_base__empty_slice_u8();
wuffs_aux::DecodeImageCallbacks callbacks;
wuffs_aux::sync_io::FileInput input(file);
wuffs_aux::DecodeImageResult res = wuffs_aux::DecodeImage(
callbacks, input,
// Use PIXEL_BLEND__SRC_OVER, not the default PIXEL_BLEND__SRC, because
// we also pass a background color.
WUFFS_BASE__PIXEL_BLEND__SRC_OVER,
g_background_colors[g_background_color_index], MAX_INCL_DIMENSION);
if (filename) {
fclose(file);
}
g_width = res.pixbuf.pixcfg.width();
g_height = res.pixbuf.pixcfg.height();
g_pixbuf_mem_owner = std::move(res.pixbuf_mem_owner);
g_pixbuf_mem_slice = res.pixbuf_mem_slice;
if (res.error_message.empty()) {
printf("%s: ok (%" PRIu32 " x %" PRIu32 ")\n", adj_filename, g_width,
g_height);
} else {
printf("%s: %s\n", adj_filename, res.error_message.c_str());
}
return res.pixbuf.pixcfg.is_valid();
}
// ---------------------------------------------------------------------
#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_mem_slice.len, g_pixbuf_mem_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)