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 =