diff --git a/recipes/pkg/flif_view/README b/recipes/pkg/flif_view/README
new file mode 100644
index 0000000..9e86801
--- /dev/null
+++ b/recipes/pkg/flif_view/README
@@ -0,0 +1,2 @@
+
+ FLIF image viewer
diff --git a/recipes/pkg/flif_view/archives b/recipes/pkg/flif_view/archives
new file mode 100644
index 0000000..54e0cc0
--- /dev/null
+++ b/recipes/pkg/flif_view/archives
@@ -0,0 +1,4 @@
+_/src/flif_view
+_/src/libflif
+_/src/libpng
+_/src/zlib
diff --git a/recipes/pkg/flif_view/hash b/recipes/pkg/flif_view/hash
new file mode 100644
index 0000000..f073713
--- /dev/null
+++ b/recipes/pkg/flif_view/hash
@@ -0,0 +1 @@
+2018-01-10 a409d1e0f61f30fa68b6a412113c8756373cd60c
diff --git a/recipes/src/flif_view/content.mk b/recipes/src/flif_view/content.mk
new file mode 100644
index 0000000..40740cd
--- /dev/null
+++ b/recipes/src/flif_view/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR := src/app/flif_view
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/recipes/src/flif_view/hash b/recipes/src/flif_view/hash
new file mode 100644
index 0000000..d9d0ff7
--- /dev/null
+++ b/recipes/src/flif_view/hash
@@ -0,0 +1 @@
+2018-01-18 5604e84db8dd5ebfc84c7e743a5fb3b9a080a92a
diff --git a/recipes/src/flif_view/used_apis b/recipes/src/flif_view/used_apis
new file mode 100644
index 0000000..c00e6a9
--- /dev/null
+++ b/recipes/src/flif_view/used_apis
@@ -0,0 +1,15 @@
+base
+os
+blit
+framebuffer_session
+gems
+input_session
+libc
+libflif
+libpng
+nitpicker_gfx
+nitpicker_session
+stdcxx
+timer_session
+vfs
+zlib
diff --git a/run/flif.run b/run/flif.run
new file mode 100644
index 0000000..331ce21
--- /dev/null
+++ b/run/flif.run
@@ -0,0 +1,96 @@
+create_boot_directory
+
+#
+# Download test FLIF file
+#
+if {![file exist bin/test.flif]} {
+ set pdf_url "https://github.com/sveinbjornt/Phew/raw/master/sample-images/train.flif"
+ catch { exec wget $pdf_url -O bin/test.flif }
+}
+
+import_from_depot genodelabs/src/[base_src] \
+ genodelabs/pkg/[drivers_interactive_pkg] \
+ genodelabs/src/init \
+ genodelabs/src/nitpicker \
+
+build { app/flif_view }
+
+install_config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+build_boot_image {
+ flif_view
+ libc.lib.so
+ zlib.lib.so
+ libm.lib.so
+ libpng.lib.so
+ libflif.lib.so
+ test.flif
+ stdcxx.lib.so
+}
+
+run_genode_until forever
diff --git a/src/app/flif_view/flif_view.cc b/src/app/flif_view/flif_view.cc
new file mode 100644
index 0000000..9a0664b
--- /dev/null
+++ b/src/app/flif_view/flif_view.cc
@@ -0,0 +1,364 @@
+/*
+ * \brief FLIF viewer for Nitpicker
+ * \author Emery Hemingway
+ * \date 2017-12-02
+ */
+
+/*
+ * Copyright (C) 2017 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* FLIF includes */
+#include
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* gems includes */
+#include
+#include
+
+/* libc includes */
+#include
+extern "C" {
+#include
+#include
+#include
+#include
+}
+
+
+namespace Flif_view {
+ using namespace Genode;
+ struct Main;
+
+ using Framebuffer::Mode;
+ typedef Nitpicker::Session::Command Command;
+}
+
+
+struct Flif_view::Main
+{
+ Main(Main const &);
+ Main &operator = (Main const &);
+
+ Libc::Env &env;
+
+ Attached_ram_dataspace back_ds { env.pd(), env.rm(), 0 };
+
+ Nitpicker::Connection nitpicker { env };
+
+ Signal_handler config_handler {
+ env.ep(), *this, &Main::handle_config_signal };
+
+ Signal_handler app_handler {
+ env.ep(), *this, &Main::handle_app_signal };
+
+ Io_signal_handler input_handler {
+ env.ep(), *this, &Main::handle_input_signal };
+
+ Io_signal_handler sync_handler {
+ env.ep(), *this, &Main::handle_sync_signal };
+
+ /* signal transmitter to wake application from input handling */
+ Signal_transmitter app_transmitter { app_handler };
+
+ Mode nit_mode = nitpicker.mode();
+
+ Surface_base::Area img_area { };
+
+ Constructible nit_ds { };
+
+ template
+ void apply_to_texture(int width, int height, FN const &fn)
+ {
+ if (nit_mode.width() < width || nit_mode.height() < height) {
+ Mode new_mode(max(nit_mode.width(), width),
+ max(nit_mode.height(), height),
+ Mode::RGB565);
+ Genode::log("resize nitpicker buffer to ", new_mode);
+ if (nit_ds.constructed())
+ nit_ds.destruct();
+ nitpicker.buffer(new_mode, false);
+ nit_ds.construct(env.rm(), nitpicker.framebuffer()->dataspace());
+ nit_mode = nitpicker.mode();
+ Genode::log("rebuffering complete");
+ } else if (!nit_ds.constructed()) {
+ nitpicker.buffer(nit_mode, false);
+ nit_ds.construct(env.rm(), nitpicker.framebuffer()->dataspace());
+ }
+
+ Genode::size_t const buffer_size =
+ nit_mode.height() * nit_mode.width() * nit_mode.bytes_per_pixel();
+
+ if (buffer_size > back_ds.size())
+ back_ds.realloc(&env.pd(), buffer_size);
+
+ Surface_base::Area nit_area(nit_mode.width(), nit_mode.height());
+ Texture texture(back_ds.local_addr(), nullptr, nit_area);
+
+ fn(texture);
+ }
+
+ void handle_sync_signal()
+ {
+ typedef Pixel_rgb565 PT;
+
+ Surface_base::Area nit_area(nit_mode.width(), nit_mode.height());
+
+ Texture texture(back_ds.local_addr(), nullptr, nit_area);
+ Surface surface(nit_ds->local_addr(), nit_area);
+
+ Texture_painter::paint(
+ surface, texture, Color(), Surface_base::Point(),
+ Texture_painter::SOLID, true);
+
+ nitpicker.enqueue(
+ view_handle, Nitpicker::Rect(Nitpicker::Point(), img_area));
+ nitpicker.enqueue(view_handle);
+ nitpicker.execute();
+
+ nitpicker.framebuffer()->sync_sigh(Signal_context_capability());
+ }
+
+ Genode::Attached_rom_dataspace config_rom { env, "config" };
+
+ Input::Session_client &input = *nitpicker.input();
+
+ Timer::Connection timer { env, "animation" };
+
+ void render_animation(Genode::Duration);
+
+ Timer::One_shot_timeout render_timeout {
+ timer, *this, &Main::render_animation };
+
+ Nitpicker::Session::View_handle view_handle = nitpicker.create_view();
+
+ void render(FLIF_IMAGE *img);
+
+ FLIF_DECODER *flif_dec = NULL;
+
+ struct dirent **namelist = NULL;
+ int page_count = 0;
+
+ int cur_page_index = 0;
+ int pending_page_index = 0;
+ int cur_frame = 0;
+
+ unsigned long last_ms = 0;
+
+ bool progressive = false;
+ bool verbose = false;
+
+ bool render_page();
+
+ void next_page()
+ {
+ while (true) {
+ ++cur_page_index;
+ if (cur_page_index > page_count-1)
+ cur_page_index = 0;
+ if (render_page())
+ break;
+ }
+ }
+
+ void prev_page()
+ {
+ while (true) {
+ --cur_page_index;
+ if (cur_page_index < 0)
+ cur_page_index = page_count-1;
+ if (render_page())
+ break;
+ }
+ }
+
+ void handle_app_signal()
+ {
+ if (cur_page_index < pending_page_index) {
+ Libc::with_libc([&] () { next_page(); });
+ } else
+ if (cur_page_index > pending_page_index) {
+ Libc::with_libc([&] () { prev_page(); });
+ }
+ }
+
+ void handle_config()
+ {
+ progressive = config_rom.xml().attribute_value(
+ "progressive", progressive);
+ verbose = config_rom.xml().attribute_value(
+ "verbose", verbose);
+
+ while (page_count > 0) {
+ --page_count;
+ free(namelist[page_count]);
+ }
+ if (namelist != NULL) {
+ free(namelist);
+ namelist = NULL;
+ }
+
+ page_count = scandir(".", &namelist, NULL, alphasort);
+
+ render_page();
+ }
+
+ void handle_config_signal()
+ {
+ try { env.vfs().apply_config(config_rom.xml().sub_node("vfs")); }
+ catch (...) { }
+
+ config_rom.update();
+ Libc::with_libc([&] () { handle_config(); });
+ }
+
+ /**
+ * I/O handler for input. May interrupt rendering during I/O
+ * dispatch because the application is not executed from
+ * this signal handler.
+ */
+ void handle_input_signal()
+ {
+ input.for_each_event([&] (Input::Event const &ev) {
+ if (ev.type() == Input::Event::PRESS) {
+ switch (ev.code()) {
+ case Input::KEY_PAGEDOWN:
+ ++pending_page_index;
+ app_transmitter.submit();
+ break;
+ case Input::KEY_PAGEUP:
+ --pending_page_index;
+ app_transmitter.submit();
+ break;
+ }
+ }
+ });
+ }
+
+ Main(Libc::Env &env) : env(env)
+ {
+ input.sigh(input_handler);
+ config_rom.sigh(config_handler);
+
+ handle_config();
+ }
+};
+
+
+static ::uint32_t progressive_render(::uint32_t quality, ::int64_t bytes_read, ::uint8_t /*decode_over*/, void *user_data, void *context)
+{
+ auto *main = (Flif_view::Main*)user_data;
+
+ if (main->verbose) {
+ unsigned long const now_ms = main->timer.elapsed_ms();
+ double dur_s = double(now_ms - main->last_ms) / 1000.0;
+ main->last_ms = now_ms;
+ Genode::log((double(bytes_read) / (1 << 20)) / dur_s, " MiB/s");
+ }
+
+ if (main->progressive) {
+ flif_decoder_generate_preview(context);
+
+ FLIF_IMAGE *img = flif_decoder_get_image(main->flif_dec, 0);
+ main->render(img);
+ }
+
+ /* abort decoding if input has moved the page index */
+ if (main->cur_page_index != main->pending_page_index) {
+ flif_abort_decoder(main->flif_dec);
+ return 0;
+ }
+
+ return ++quality;
+}
+
+
+void Flif_view::Main::render_animation(Genode::Duration)
+{
+ cur_frame = (cur_frame+1) % flif_decoder_num_images(flif_dec);
+
+ FLIF_IMAGE *img = flif_decoder_get_image(flif_dec, cur_frame);
+ render(img);
+ auto x = Microseconds(flif_image_get_frame_delay(img)*100);
+ render_timeout.schedule(x);
+}
+
+
+void Flif_view::Main::render(FLIF_IMAGE *img)
+{
+ int const f_width = flif_image_get_width(img);
+ int const f_height = flif_image_get_height(img);
+
+ typedef Pixel_rgb565 PT;
+ apply_to_texture(f_width, f_height, [&] (Texture &texture) {
+ /* fill texture with FLIF image data */
+ char row[f_width*4];
+ for (int y = 0; y < f_height; ++y) {
+ flif_image_read_row_RGBA8(img, y, &row, sizeof(row));
+ texture.rgba((unsigned char*)&row, f_width, y);
+ }
+ });
+
+ /* flush to surface on next sync signal */
+ img_area = Surface_base::Area(f_width, f_height);
+ nitpicker.framebuffer()->sync_sigh(sync_handler);
+}
+
+
+bool Flif_view::Main::render_page()
+{
+ pending_page_index = cur_page_index;
+
+ struct dirent *d = namelist[cur_page_index];
+ char const *filename = d->d_name;
+
+ if (flif_dec != NULL) {
+ flif_destroy_decoder(flif_dec);
+ flif_dec = NULL;
+ }
+
+ flif_dec = flif_create_decoder();
+ flif_decoder_set_resize(flif_dec, nit_mode.width(), nit_mode.height());
+ flif_decoder_set_callback(flif_dec, &(progressive_render), this);
+
+ nitpicker.enqueue(view_handle, filename);
+
+ if (verbose)
+ last_ms = timer.elapsed_ms();
+
+ if (!flif_decoder_decode_file(flif_dec, filename)) {
+ Genode::error("decode '", filename, "' failed");
+ return false;
+ }
+
+ Genode::log(filename);
+
+ FLIF_IMAGE *img = flif_decoder_get_image(flif_dec, 0);
+ render(img);
+
+ if (flif_decoder_num_images(flif_dec) > 1) {
+ render_timeout.schedule(
+ Microseconds(flif_image_get_frame_delay(img)));
+ }
+
+ return true;
+}
+
+
+void Libc::Component::construct(Libc::Env &env) {
+ with_libc([&env] () { static Flif_view::Main application(env); }); }
diff --git a/src/app/flif_view/target.mk b/src/app/flif_view/target.mk
new file mode 100644
index 0000000..6c9a4a9
--- /dev/null
+++ b/src/app/flif_view/target.mk
@@ -0,0 +1,5 @@
+TARGET += flif_view
+LIBS += base libflif libc blit stdcxx
+SRC_CC = flif_view.cc
+
+CC_CXX_WARN_STRICT =