From 61b370ecba24b1ff1bc955d0673ef7d6efcbac43 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Thu, 14 Aug 2014 16:25:29 +0200 Subject: [PATCH] gems: new backdrop application The new backdrop found at gems/src/app/backdrop replaces the old program that was hosted in the demo repository. --- repos/demo/src/app/backdrop/README | 19 - repos/demo/src/app/backdrop/main.cc | 269 ------------- repos/demo/src/app/backdrop/target.mk | 4 - repos/gems/run/wm.run | 70 +++- repos/gems/src/app/backdrop/README | 76 ++++ repos/gems/src/app/backdrop/chunky_texture.h | 62 +++ repos/gems/src/app/backdrop/file.cc | 55 +++ repos/gems/src/app/backdrop/file.h | 50 +++ repos/gems/src/app/backdrop/genode_logo.png | Bin 0 -> 19768 bytes repos/gems/src/app/backdrop/grid.png | Bin 0 -> 582 bytes repos/gems/src/app/backdrop/main.cc | 375 +++++++++++++++++++ repos/gems/src/app/backdrop/png_image.h | 181 +++++++++ repos/gems/src/app/backdrop/target.mk | 3 + repos/gems/src/app/backdrop/texture.cc | 79 ++++ repos/gems/src/app/backdrop/texture_utils.h | 97 +++++ repos/gems/src/app/backdrop/xml_anchor.h | 71 ++++ 16 files changed, 1100 insertions(+), 311 deletions(-) delete mode 100644 repos/demo/src/app/backdrop/README delete mode 100644 repos/demo/src/app/backdrop/main.cc delete mode 100644 repos/demo/src/app/backdrop/target.mk create mode 100644 repos/gems/src/app/backdrop/README create mode 100644 repos/gems/src/app/backdrop/chunky_texture.h create mode 100644 repos/gems/src/app/backdrop/file.cc create mode 100644 repos/gems/src/app/backdrop/file.h create mode 100644 repos/gems/src/app/backdrop/genode_logo.png create mode 100644 repos/gems/src/app/backdrop/grid.png create mode 100644 repos/gems/src/app/backdrop/main.cc create mode 100644 repos/gems/src/app/backdrop/png_image.h create mode 100644 repos/gems/src/app/backdrop/target.mk create mode 100644 repos/gems/src/app/backdrop/texture.cc create mode 100644 repos/gems/src/app/backdrop/texture_utils.h create mode 100644 repos/gems/src/app/backdrop/xml_anchor.h diff --git a/repos/demo/src/app/backdrop/README b/repos/demo/src/app/backdrop/README deleted file mode 100644 index 3c1d2ab611..0000000000 --- a/repos/demo/src/app/backdrop/README +++ /dev/null @@ -1,19 +0,0 @@ -This directory contains a simple backdrop program for Nitpicker. - - -Usage ------ - -You have to specify the name of the PNG file to be used as background -image via a declaration in your config file: - -! -! background.png -! - - -Limitations ------------ - -The PNG file is expected to be equal to the screen size. No scaling -or tiling is supported. diff --git a/repos/demo/src/app/backdrop/main.cc b/repos/demo/src/app/backdrop/main.cc deleted file mode 100644 index bbd1269af0..0000000000 --- a/repos/demo/src/app/backdrop/main.cc +++ /dev/null @@ -1,269 +0,0 @@ -/* - * \brief Backdrop for Nitpicker - * \author Norman Feske - * \date 2009-08-28 - */ - -/* - * Copyright (C) 2009-2013 Genode Labs GmbH - * - * This file is part of the Genode OS framework, which is distributed - * under the terms of the GNU General Public License version 2. - */ - -/* libpng includes */ -#include - -/* Genode includes */ -#include -#include -#include -#include -#include -#include - -using namespace Genode; - - -/*************** - ** Dithering ** - ***************/ - -enum { DITHER_SIZE = 16, DITHER_MASK = DITHER_SIZE - 1 }; - -static const int dither_matrix[DITHER_SIZE][DITHER_SIZE] = { - { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, - { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, - { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, - { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, - { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, - { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, - { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, - { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, - { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, - { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, - { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, - { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, - { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, - { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, - { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, - { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } -}; - - -static inline uint16_t rgb565(int r, int g, int b) -{ - enum { - R_MASK = 0xf800, R_LSHIFT = 8, - G_MASK = 0x07e0, G_LSHIFT = 3, - B_MASK = 0x001f, B_RSHIFT = 3 - }; - return ((r << R_LSHIFT) & R_MASK) - | ((g << G_LSHIFT) & G_MASK) - | ((b >> B_RSHIFT) & B_MASK); -} - - -static void convert_line_rgba_to_rgb565(const unsigned char *rgba_src, - uint16_t *dst, int num_pixels, int line) -{ - enum { CHANNEL_MAX = 255 }; - - int const *dm = dither_matrix[line & DITHER_MASK]; - - for (int i = 0; i < num_pixels; i++) { - int v = dm[i & DITHER_MASK] >> 5; - - *dst++ = rgb565(min(v + (int)rgba_src[0], (int)CHANNEL_MAX), - min(v + (int)rgba_src[1], (int)CHANNEL_MAX), - min(v + (int)rgba_src[2], (int)CHANNEL_MAX)); - - /* we ignore the alpha channel */ - - rgba_src += 4; /* next pixel */ - } -} - - -/************************ - ** PNG image decoding ** - ************************/ - -class Png_stream -{ - private: - - char *_addr; - - public: - - /** - * Constructor - */ - Png_stream(char *addr) { _addr = addr; } - - /** - * Read from png stream - */ - void read(char *dst, int len) - { - Genode::memcpy(dst, _addr, len); - _addr += len; - } -}; - - -/** - * PNG read callback - */ -static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t len) -{ - Png_stream *stream = (Png_stream *)png_get_io_ptr(png_ptr); - - stream->read((char *)data, len); -} - - - -static void convert_png_to_rgb565(void *png_data, - uint16_t *dst, int dst_w, int dst_h) -{ - Png_stream *stream = new (env()->heap()) Png_stream((char *)png_data); - - png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); - if (!png_ptr) return; - - png_set_read_fn(png_ptr, stream, user_read_data); - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL); - return; - } - - png_read_info(png_ptr, info_ptr); - - /* get image data chunk */ - int bit_depth, color_type, interlace_type; - png_uint_32 img_w, img_h; - png_get_IHDR(png_ptr, info_ptr, &img_w, &img_h, &bit_depth, &color_type, - &interlace_type, int_p_NULL, int_p_NULL); - printf("png is %d x %d, depth=%d\n", (int)img_w, (int)img_h, bit_depth); - - if (color_type == PNG_COLOR_TYPE_PALETTE) - png_set_palette_to_rgb(png_ptr); - - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) - png_set_gray_1_2_4_to_8(png_ptr); - - if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png_ptr); - - if (bit_depth < 8) png_set_packing(png_ptr); - if (bit_depth == 16) png_set_strip_16(png_ptr); - - /* allocate buffer for decoding a row */ - static png_byte *row_ptr; - static int curr_row_size; - - int needed_row_size = png_get_rowbytes(png_ptr, info_ptr)*8; - - if (curr_row_size < needed_row_size) { - if (row_ptr) env()->heap()->free(row_ptr, curr_row_size); - row_ptr = (png_byte *)env()->heap()->alloc(needed_row_size); - curr_row_size = needed_row_size; - } - - /* fill texture */ - int dst_y = 0; - for (int j = 0; j < min((int)img_h, dst_h); j++, dst_y++) { - png_read_row(png_ptr, row_ptr, NULL); - convert_line_rgba_to_rgb565((unsigned char *)row_ptr, dst + dst_y*dst_w, - min(dst_w, (int)img_w), j); - } -} - - -/**************************** - ** Configuration handling ** - ****************************/ - -/** - * Determine PNG filename of image to be used as background - * - * \param dst destination buffer for storing the filename - * \param dst_len size of destination buffer - * \return 0 on success - */ -static int read_image_filename_from_config(char *dst, Genode::size_t dst_len) -{ - try { - Xml_node image_xml = config()->xml_node().sub_node("image"); - image_xml.value(dst, dst_len); - return 0; - } catch (Xml_node::Nonexistent_sub_node) { - printf("Error: Configuration has no 'image' declaration.\n"); - return -2; - } -} - - -/****************** - ** Main program ** - ******************/ - -int main(int argc, char **argv) -{ - enum { PNG_NAME_MAX = 128 }; - static char png_name[PNG_NAME_MAX]; - - if (read_image_filename_from_config(png_name, sizeof(png_name)) < 0) - return -1; - - printf("using PNG file \"%s\" as background\n", png_name); - - static void *png_data; - try { - static Rom_connection png_rom(png_name); - png_data = env()->rm_session()->attach(png_rom.dataspace()); - } catch (...) { - printf("Error: Could not obtain PNG image from ROM service\n"); - return -2; - } - - static Nitpicker::Connection nitpicker; - - /* obtain physical screen size */ - Framebuffer::Mode const mode = nitpicker.mode(); - - /* setup virtual framebuffer mode */ - nitpicker.buffer(mode, false); - - static Framebuffer::Session_client framebuffer(nitpicker.framebuffer_session()); - Nitpicker::Session::View_handle view_handle = nitpicker.create_view(); - - if (mode.format() != Framebuffer::Mode::RGB565) { - printf("Error: Color mode %d not supported\n", (int)mode.format()); - return -3; - } - - /* make virtual framebuffer locally accessible */ - uint16_t *fb = env()->rm_session()->attach(framebuffer.dataspace()); - - /* fill virtual framebuffer with decoded image data */ - convert_png_to_rgb565(png_data, fb, mode.width(), mode.height()); - - /* display view behind all others */ - typedef Nitpicker::Session::Command Command; - nitpicker.enqueue(view_handle); - Nitpicker::Rect rect(Nitpicker::Point(), - Nitpicker::Area(mode.width(), mode.height())); - nitpicker.enqueue(view_handle, rect); - nitpicker.enqueue(view_handle); - nitpicker.execute(); - - framebuffer.refresh(0, 0, mode.width(), mode.height()); - - sleep_forever(); - return 0; -} diff --git a/repos/demo/src/app/backdrop/target.mk b/repos/demo/src/app/backdrop/target.mk deleted file mode 100644 index ef4c951a98..0000000000 --- a/repos/demo/src/app/backdrop/target.mk +++ /dev/null @@ -1,4 +0,0 @@ -TARGET = backdrop -SRC_CC = main.cc -LIBS = base libpng_static libz_static mini_c config -CC_OPT += -DPNG_USER_CONFIG diff --git a/repos/gems/run/wm.run b/repos/gems/run/wm.run index 7bc8fdc0e1..5aff6d5c46 100644 --- a/repos/gems/run/wm.run +++ b/repos/gems/run/wm.run @@ -9,6 +9,7 @@ set build_components { server/nitpicker app/pointer server/report_rom drivers/framebuffer drivers/pci drivers/input test/nitpicker + app/backdrop app/launchpad server/nit_fb } @@ -153,34 +154,58 @@ append config { - - - - - - - - - - - - - - - - + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -190,6 +215,10 @@ append config { install_config $config +# copy backdrop PNG images to bin directory +foreach file { genode_logo.png grid.png } { + file copy -force [genode_dir]/repos/gems/src/app/backdrop/$file bin/ } + # # Boot modules @@ -202,7 +231,10 @@ set boot_modules { wm decorator floating_window_layouter nitpicker pointer report_rom + backdrop testnit launchpad nit_fb + ld.lib.so libpng.lib.so libc.lib.so libm.lib.so zlib.lib.so + genode_logo.png grid.png } # platform-specific modules diff --git a/repos/gems/src/app/backdrop/README b/repos/gems/src/app/backdrop/README new file mode 100644 index 0000000000..ef0533282e --- /dev/null +++ b/repos/gems/src/app/backdrop/README @@ -0,0 +1,76 @@ +This directory contains a backdrop program for Nitpicker. It composes a +background image out of a solid color and an arbitrary number of PNG image +files. It is able to dynamically respond to configuration changes as well +as a changed screen size. + + +Configuration +~~~~~~~~~~~~~ + +A typical example configuration looks as follows. + +! +! +! +! +! +! +! +! +! +! +! + +Backdrop obtains PNG images file from the libc VFS. In the example, the VFS is +configured to present two ROM modules as files in the root directory. Those +files are then referred to by the subsequent '' nodes. + +The final background image is generated by applying a number of graphical +operations in the order of their appearance in the configuration. The +'' operation fills the entire screen with the solid color as +specified by the 'color' attribute. The default color is black. + +The '' operation loads a PNG image from the VFS, scales and positions +it according to the '' attributes, and paints it either as tiles +covering the whole screen or as a single image. The supported attributes are + +:tiled (default "no"): + + If set to "yes", the image will be used as tile to cover the entire screen. + +:alpha (default is opaque): + + A number in the range of 0..255 that specifies the opacity of the image + when painted. + +:anchor (default "center"): + + Defines the screen position that is used as a basis for positioning the + image. Supported values are "top_left", "top", "top_right", "left", + "center", "right", "bottom_left", "bottom", and "bottom_right". + In the example above, the _genode_logo.png_ image will be positioned + in the bottom-right corner of the screen. + +:xpos and ypos (default "0"): + + The 'xpos' and 'ypos' attributes define the position of the image relative + to the anchor. It is meant for allowing the pixel-perfect fine tuning of + positions. + +:scale (no scaling by default): + + The 'attribute' specifies the way of how the image should be adjusted to + the screen size by the means of proportional scaling. Possible values are + "fit" and "zoom". The former scales the image such that the entire image + is visible on screen. The latter makes sure that the entire screen is + filled with (a viewport of) the image. When using the "zoom" value, the + viewport can be defined via the 'anchor', 'xpos', and 'ypos' attributes. + + +Example +~~~~~~~ + +Please refer to the _gems/run/wm.run_ script for a practical example of +using the backdrop. + diff --git a/repos/gems/src/app/backdrop/chunky_texture.h b/repos/gems/src/app/backdrop/chunky_texture.h new file mode 100644 index 0000000000..f7564da22f --- /dev/null +++ b/repos/gems/src/app/backdrop/chunky_texture.h @@ -0,0 +1,62 @@ +/* + * \brief Texture with backing store for pixels and alpha channel + * \author Norman Feske + * \date 2014-08-22 + */ + +/* + * Copyright (C) 2014 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _CHUNKY_TEXTURE_H_ +#define _CHUNKY_TEXTURE_H_ + +#include +#include + +template +class Chunky_texture : Genode::Attached_ram_dataspace, public Genode::Texture +{ + private: + + typedef Genode::Surface_base::Area Area; + + /** + * Calculate memory needed to store the texture + */ + static Genode::size_t _num_bytes(Area size) + { + /* account for pixel size + 1 byte per alpha value */ + return size.count()*(sizeof(PT) + 1); + } + + /** + * Return base of pixel buffer + */ + PT *_pixel() + { + return local_addr(); + } + + /** + * Return base of alpha buffer + */ + unsigned char *_alpha(Area size) + { + /* alpha buffer follows pixel buffer */ + return (unsigned char *)(local_addr() + size.count()); + } + + public: + + Chunky_texture(Genode::Ram_session &ram, Genode::Surface_base::Area size) + : + Genode::Attached_ram_dataspace(&ram, _num_bytes(size)), + Genode::Texture(_pixel(), _alpha(size), size) + { } +}; + +#endif /* _CHUNKY_TEXTURE_H_ */ diff --git a/repos/gems/src/app/backdrop/file.cc b/repos/gems/src/app/backdrop/file.cc new file mode 100644 index 0000000000..23fc98734e --- /dev/null +++ b/repos/gems/src/app/backdrop/file.cc @@ -0,0 +1,55 @@ +/* + * \brief Utility for loading a file + * \author Norman Feske + * \date 2014-08-14 + */ + +/* + * Copyright (C) 2014 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include + +/* local includes */ +#include "file.h" + +/* libc includes */ +#include +#include +#include +#include +#include + + +static Genode::size_t file_size(char const *name) +{ + struct stat s; + s.st_size = 0; + stat(name, &s); + return s.st_size; +} + + +File::File(char const *name, Genode::Allocator &alloc) +: + _alloc(alloc), + _file_size(file_size(name)), + _data(alloc.alloc(_file_size)) +{ + int const fd = open(name, O_RDONLY); + if (read(fd, _data, _file_size) < 0) { + PERR("reading from file \"%s\" failed (error %d)", name, errno); + throw Reading_failed(); + } + close(fd); +} + + +File::~File() +{ + _alloc.free(_data, _file_size); +} diff --git a/repos/gems/src/app/backdrop/file.h b/repos/gems/src/app/backdrop/file.h new file mode 100644 index 0000000000..bf32b5b90f --- /dev/null +++ b/repos/gems/src/app/backdrop/file.h @@ -0,0 +1,50 @@ +/* + * \brief Utility for loading a file + * \author Norman Feske + * \date 2014-08-14 + */ + +/* + * Copyright (C) 2014 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _FILE_H_ +#define _FILE_H_ + +/* Genode includes */ +#include + +class File +{ + private: + + Genode::Allocator &_alloc; + Genode::size_t const _file_size; + void *_data; + + public: + + /** + * Exception type + */ + class Reading_failed { }; + + /** + * Constructor + * + * \throw Reading_failed; + */ + File(char const *name, Genode::Allocator &alloc); + + ~File(); + + template T *data() { return (T *)_data; } + + Genode::size_t size() const { return _file_size; } +}; + +#endif /* _FILE_H_ */ + diff --git a/repos/gems/src/app/backdrop/genode_logo.png b/repos/gems/src/app/backdrop/genode_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..816dfaf0a2c2b45d845fceff75e3f581a0f7c7a2 GIT binary patch literal 19768 zcmb4rhc{erxb@MZmqC=M8KOk==sg)BYLuu!5QI^p_mUt+2@$;~qD1dR8AOdXgc#9A z^ltQte$Vgw*8KzSvN*G5t>K)v?EO4@?^pCQ9W_d_J7fR=P(D?M=>q^BJ@{Ia`+)>@ZKYi5DkD%6EI6tBtXu*W|#m+feG?H~9`rIR?p5|HElm7iCnwK^7hjkRY*rcg3AEx!~YBv+`xy=Sv`}h*P z_o%4D3O+%WE_eFXK=+({t$pkap9`!V&BTX#1H3bMi1rShXCii zDA9k<8+k~XY#gVaDLq(+Iu+sB($mvxpI~;#J{YRG*po{`d=Pclg_RS8DJ$Dlgj2FD zVZmW~l&jq(m*((+zlLYeTDxshd%{hAF-D;BUqNZ@x#xkROU0(g0@7D;vT7k!O$JVe z2gr5hf+PF*OArCUP}IGzS}n1RQ{840N9Ws>$o=N^T=R=g0`axG)@I5Y@v>Wp@cwu< zbHCq-QMwFa%796a?{L`v|M-XT+PltLFCK74>LqVB;4A}K$B}_eU1gOmYcjyM+i!b* zsc|%1xHMs8gZAc{Hyw-f{LB3G^mrH0#0xi-p!<{rCoRwZ;odqYG#io z{(^$tQ1bAT(hGV2nfi(KN!M?)%35cEyUiEX%+!kCujZOPC+K)|+LJOm9>KabBwkm1 zF?edc+D6^#vweFCU7ixp|LZN)rPrl#QY}Wt?!zXn>ZU7Cd_Zm>%Cw&?aL0||f%)ks z8GslX96X=*nRSsH&5@H*2WO+!j8IKnle z?!xe8!F)dPeZ&Bru8XVd*5Ti{j4B%a?!Q06Z=rJJ);nFqcW$T`iaTYp){#@_KL3d9dXyMM3?PB3 z4&OgSscLLLgxCR`4dq+5I0dr%{7cJoHe@-tGvFnlOfTT36{o>(UHHqiEZdg>@1yBk zgxWfi3-$ySt_}e2BKTosZJsgdE~}rY$5(H2ne-3?pFLu9+dS_=d!ZPivU6z*OVM{7 zy3CE)NnFDzz9@(|;@>x?%|}wzK}^QRLLN#l<;ipE$bfnY9)8Xg@Ex5?D48+CZ( zRDd6JpVYcYfIAK2C$c0gG$qW@nZ^&y^$jHbq>TkFHYBZcTK%xLczmMnMPsJM6d;I- zpk!966gUq|b)o?PF&&H3fU$D=VOeQwXuen%vLsXKlZuYf0`wV!J;K3RZ@H}(-? zE`%`6^4rdD{j5j;GZ+N~3RIIw1N+Sf^-z4MCCvbhrv^Odt`Rd&sS3Ap7gOR(1F|OP z{Wp?*Uie{9_>w$a3lHGN1927>26W!Vj}D`Qs1Bn7i8;d%e9(8z4iIP@qB;JfAbkF_ z$z*8Wi)(?$x#(D^!vUYv&!Q73_`J+?z6U68DW0uX5LDRxKoH1#u3;{WQ9Af%+h1vk zl)a-AvP95xd>aB^e?UFmV)GJicbL|3S-2Ta%^;}5eKh8ucXpS-%QI2}mD$FZv_}Sz z(y7gtH}s-2g&s$_B3FgD)K2#>$iQO7;EQ8b01xoPx`ke(f4mfInz!67pMF_Uq0)0Q z(Dtq%iBa>C>dMDtfwL0A`WX5L_X#ojm?!yq(?qb7$tco9RYQcHL83Eh+2^2e`KJwpQ9i zQLb2zS#i@sNNdHt3)lTKMysG2pOKc9Da+8{RW**_`gl{fFhCgO0bx-^$wkPQ{OQm&7oqeV(r)W?8;t7HJk|LcxrkgfB`}T?%%MF z(86$Ujgl%t7+2W+Dki-;{(Dze70*gjg}ek~W#{L9lan6x#O_*|{UQWD41<94vO+NC z>o>mmuZj+N-fI~NO4t8v9>pMW|9a>W*vJ79*SQGdWV8f=5Oqi5yUm;Fo#{F%H$9#- ztB~IHDT~nVoPk#%3gRpWxdLfHe{ZEyYtnjNV)Y+@tWsCUBOIS^v+bPdTj|T{Ae-kg z_*8D^dpA|;+E)v?fjb9qAZ*@ufpTkW3v+fP?zxbCG@PvgJAaq{JYD@3=HfkA zQY@#EJ{`t0$*M|?3wAy<2 ztsghZp8omz#HD#P)mG9wPbWtfkAmQjZNfY!ovJ2B$iKw(o5M7RvgqG~B3}L&qVjsz zmqy+dH>3X4nC7;x@!6MUY#|L-(zaUofQVF)U~KE;AN+bhl4eywtDu9`^@P#PhF1su zn(fatsvL~qfHIM_Nvyi}Zr-GSRd!SF)C=$F2vm;d`<|Mo_Eib{4Pj=l$o_0Gdz@3i z3RbhrUxX_cTV{0po$hDPyE2BtlQ%MNWT=TiS{WdAExQg6kd#D!fGMlNPVRC~L0^6a z3fk(<-04$bdiLb-Wj6M}otn|o(o!RWFopBnyh{VgjqJg8+L@dz;X5BrP<#JBa}Xhvfj zt>2*==-aF7Ild@S>+g=Lhk~)zQC|~eZK4!>7EYQCigW^>68~_3G11m?<$rhH-#45b zk3=!i$vGT-h?YxBPNv}hIlAEDiPTiF{z1FG%L{y@#TuY8FPHMWDh{PETuOnKDtMQR zOXPE^j8>;&kSFEd1|XCSpmPn&4hCYyHiK;Q_j2UYC&?Dfyn_Ay%0N=8Wyo1df{^Lq z058yK`9hC|`{m2C>K2A7jW*#%$-UIPJmbt8y8b$YVjVdu7D@W8t?h-}kY(-BkmG|- z^1ztoi;wCno8U5^YG8^4FX3LLGD`7Zb6NViLyvw|S69ER5&cCrspEJp@GqhCF@F`) zk1I_9SyjD&XQd_930g&GO?t`f=(*qDz3)#xl%D)u4PFghbND&rzX##?1}Q2_E3Y2` z`x{>q*k$aGH!AuP^4?UTf0#jQF|Dp3TCJ$mIgHMH1?TV|uH$B&-EZti!bqUq^^pd# z#PE#tdJI^m%E#xoXJ`c|v#Q=hPrvk>DhhIWx?HK((i}){cb?;M7~N_SbPzdp$z(Tv z$K17fHN7}DXzAUL@S!OC@y%R!))RM(?+I!(g_0d*whaJ>IieQj644`Pic3VjnvdFO z0B*1XE%7RSW3X;yXt?93^_w?o;27lXItd;(gJ8!tPoenyG1FNI$)b^e#S}L0{R=?%ybha{G$_?XSZ0WsgfnXv) z_@Gjglj(2pA!R**$bR-#>O120UJD1yyr`E(4bAX-@rqmDkl zg+|G9vu%<7v=nKFjbq2+-1_axR#)%8Z=Q0vDitLGQ-Y)MVv-TAV~da0{I6ZsZiLl7+% z9tWz$mL3CkO%JXL$jYM)y6&^xcR_l-{O}8)SwXapyl=Z~ds6uOK7h!sn~62v7kn;1 zJhyu~>w0$bdqT&px(_Wf%QFMZR7^u@BS;pd=uOaezNk+)c60iA@uE)CDVIOhcORqV zush_4Z{}HVel(xl{Oea*Fh$yHPq6vU)v)8);{6Toj~CpSa2UE&z|AeMSM<{XFB9f{ zv)%!R{Yvv^3_!X_qR8-lzAC1~r&3b4{SSVu4cgP_HGPejJuKI~UnXSU;>910Q0X!Z zi>3z(ROQ3|6(AyNGm`lH2VT{oe+0lZGNcC=SS`;&;~s%K=hwX)Cm}KS-&U}P5Gg|= z!NB!ogQA&*o9lO^nhe4%YXwOXGdM&!B_)k<7o>WEz7JXZ)%v9j z09kKf;O|7Wo#5??CJL)&kHQ!3t0}P@0UL?M-6AgtlqnG;sA=}_TPdlz-fNplxM_zx zd?3u?$!^e37Pq!wVbLjGY2k>GdKMYAuKVlGjBw|wFOM79Wx+`-ZdAe|ao3VK>a-V! z{B@3)S{lk@Sl-cAk<(9V@J^&{4yyw64$hT1PDd2J8gV{n5q89Ca>$e4vrjrm|a`{z(>;#l;k9&JUn z{YWM?mC_~Rj!mWAnuJyxI}QI(yauW@@#RjsXpUL}GxGUO$kplVtD7_np8-;0Hou>g zFrLTnvk=c)K+<4Je56bZT=spJKOXC@8crAG;>q_e|LychOh%`772P-jv8JlsaY;2I z*5YR?r+T-q$wmlv0Z{vtv%G_6g91;u#WM`8=ihrTCzGLR^pXwk!|#`sF62DiMCiJ; z9kht&`mc?4Muorseb-c4mNuE1l5#sY=Um#sX+hn)+GGvN1Z%L7aS2eCS5zi zsO|g6Mow5m;~ypiXRY5ymq#d>mfHrm5G3HtubblL0g8i9)$DlN(ccr(@p_VQ zv;kPM^yQ^MY;(t6!<1ze^1201ZM#+p#lLs&o?LFwcr?d?`_%jhp8D5@#_KIYh zRQ;o7Z)R*!2h-4nEABE2h@ZPg(lE(fi&GZJW^ zYX2?!FW(L?mV_qB1HH*;m6>*PNiG~B^XBDdElXv`-p3C(+r86%B}~f@zd^CfGYzQ# zhiwJD7^ZSRY^_Fq1%>x#@tzlSgo|m`(6bD`5@JC4(E%zD_8Suzi%8~*F#og2M?xnt z+;%tZTDO5I;YK5J{L_GAG#yBpB1ul{+!*Q@u@Wo(iiIZyx7Y}u$V*n^%P$YtxtV-_ z^UGSmsQl*x^TwcNL6lWtqwT7T!Y?Y1lKG*N(x06)A2{<)ZxNcm|-yA_yfdNaZuvMbW`rHvm z>+0HOaZs?X!s{7F0|8g$ZbHLgsoA!(D8Pr6cPg~lUMKG!bN6uOk8XmJOZF#X-JY?XIPK8r2iM%}iSRtVJt=!T`*wU#D2N9Lx zgy8>_dX=ulJ)u#OViq+-8{MWsSnfE$z{QClR)?y!NzuG`^?|$21NaEd=i}}EP0i16 zned!XkbkK-`x&%!F6^Q$>pd&q$#EHoj8d_;g&I4k>M}Bq`;}94*Rdl-z<=5&)~& z!c1UExD9ThwRD%p>7tQxs|KeO`OE!o0oRr@v=T@)+ZoTfm%fgyeeuh~<_P%odC469 z?52LLz%Q2ZdNaotbK;+_{(!u@trP}5W3DSz>oU9uhyW$nPq1X3d?sKclF_ouJ&Nu0 z#X@_qx&GJrz7KNc>l3=8XZp&20y+GJ@uw#z?8SIK4IoS>Z>b!{zU#`@FbW7R)Bh-W zwgifI>i)eXlt3fco{XcUX&^FqD)vMb*czwF(nwu}lVq8*l_aV6v|vfox}YsJf9S}n zMrlUu4LqBQ*%)C+C1a}a&RV}`Ll1d_dPHx>ghyTx9jMcB@Sd$(WC!C^K4t0mOI3h2 zdgYLl#CU`AQfvDqrRd{}VSTVKaMZ28JM^3+=zOzyF<+HKp=P&nHf}b(a=|kp2OOPL zCtK|gK8>hYgJFu{xLx`fkd20&w4sSj4>PQu5dnW}q#@C&^5(W=rkj;BgdlC&T}~QX zA*&~i%y%a|a>PgFmmT`A*S-ti-qz`9L(5G)vYg)%_onV+ahnaH-S~)kQC8M188XKN4R z0T08JqY=W2=ij(|kvDIN7i^a9C~L*457>f0veP*0n&rFygSg}Lu+7-g)<}DpRc^fk z;Uyb%*z>qZTh65weU}R845wB!@x+{Uv}T!BjLTkX5UYH}VOpF|mYa+|Un30NY1H&y zmh(^B&q9!7WMqx5!9nN8SvRHimTZIr&sCTzjm)OF^qma6MVg)ZygJ6osJdYQaD2|< z#=sgl{EG!HNF}#37UH=kf1&S_NDzaw1o$W(a}fTRBIhJ|05t>l5i2vh4vE@Kct?VL zKX99JA3<4E>#E|s+`7R)tCezs_3SA#&8!28 zZfEWL`EceA^n3AsH~;fN1myM7CLIHg;e1AgYz2Kqe!o7(b(#TJ!Z990&4k<8n8}g5Z?x6(zdh+zm8rc4&$vuelYWHJukHVYztnQVPJ8x*~Lk+ zZMta>{FL7QWagRci~di>-OPatfu;9(Nu=u%QN;&x{ z67DY6gFE=j&n!iu!@a-KXJZL~v1wC-A&n*zIj*Ie!hnaont86S_*nVZg);#v(GjWz zeYWumjhiLBd2{Jc&sK$-^7iW%yVF9!CR&!ew4v|e{R)*f&R00Wv(wS6lV_a>k}PQ> zX#4Bd-4KkRl2WMpc1<((`l3T$V=CsK8M>vY#p9PRgY$or&u*d^7tUGkiT+LNX&{>u zQt|EE>$FtQJ~p2}VWxF?m-2%Jtu!?>bdK|qE5B6!s=(Ts7WQwoLyQFG5j>dB$)&+I z=%lhy>E&Z|RG?Po!k-yiQaP5kr^tmbOar0`qj;NjF&5`g8k8} zck0RaS``3gPD>k`_o2p%Mdvr=TAz60A7O)W(lG&dZR!~L<$?|)^u+BIFT;+|e1&M~ zs4|D)AW+5c9Fbb6-vwPQo)-a|g+rK-i=5HYvNGeCewy*Cn$< z`b>RAJzZQ4%qf80)?Hbr-ykqG=)$X3@@_7Rk=O58FlN<%PmI%_2d8VHA^LqXY4~BV zo%~0(`6!TC@2_qnNN5JGf5#wSfI*63diAr;CR@9NSJ#v>#j z+;}x#T^w9u|#mPglrpccyzOwUW}KT__cfdBA{hWR<@pA_0X-U2s)eWm|;PKOZ2ccF&m zwnmK%rV2@f#LVqQz79xw`s| z;n2bOI9?<=v%ptHzeE`prqUC&`)Z?nqz-*L_)6aq5*^*Fr$53T9YNA-N^C}qnPEc6 zaK2ja1x+MO>CNYQ{rlR(-W6@%ZrDK#sn2&{RZ|=+7;HNtg;2u=;k%3!9Ev-aTnX7h zzd^`8W`ao2)Yzk0*t12Ctbxufw|2*@SfQ40Uc-Na)1dAOh(9W;c2|3C%kfv0ZHJ`^ z_QTm{@ARDrfFc?_nf0SldD|H`l>E^!UWNasDE5GjI+o&vW2U;wUuJH~D|ZfP{SHYX z{55E9o;W41%Vd6o5bx<#R$l2d1On+viucrK?}ZrC!8QWk8#CGt z>$zp`3Ra%YCzdf_jX8Aa zu-44b^k-Dx+bs1CtC+W2f6-;u8tM0TCzr-~*wvXU0impgFPh&j8_IDic(I2m#RUDo}5s{nhV!fDwkj#o+MLU7${x-9M;y@=Ex?y zw+7;9w4p4;fHFsDm0DER@mUb<(td|(pUx2GJ9+e*CW&(yA-V| zf9%h=^pO)pn)>!jpN*nR+O-yq>pwskF5O9KIJL|c(tErZexKkGuUhH{558AjutEa+sK1H zmdVMw~?gm zKn!-z!DzdlYKpdoG4U3RLbV|2!}80tL@u1{K@Wc*HHYF00$}HOo6Yw>bn%bzMjk14 zfIj`at~(tPM+%T2ajb+YRp@@BDofB^yOn5DXxMV{Z`G%sLB=6ATOy|}YfPRdQT5Sk z`P&y7jJ8>5VC6$J`QZ`y#8NLwz?keacX}DZH`Yw`?!VMN+<9JJ|L(HwveFu=_bo*X zv6ADd!Ee{*cMGD-jYaZ8kHbjL&WJ{;hO=%KJ%!u8XPYor7cm1S%3uXDoBEAH9wG%X z7+Fe4`*%-M$$!zIJBn?>;}R^sbG>e^sOKD$9Sfua*Q zw~{;>PV=YPhWDT6xg2%AM~s7h-t}KYK4@?9{78->wr!z8VZW1LhIPTUb@z}_=#uXZ z>g45G@_CWeT3rx*BhTXwa!2Ju;PrThm&Mbk#YT%sw+=oj_lyN{yb@o0J(Dm{*}7}? zQJq^0nz#Xpur5|6zD3g`H>$n?5ivL824EX?OECTie9qap!An))wtpTHVP@vdE8!z( z;*Ev;XEaYwge%ko){&xm#@OKTI}=G6@SM8VloZOJWtEoK?Bd0+0=*JIN6~KUu`Vjt z`?%V=TPRch>}MIm6k<&PY+Z1qyGw<6*{oG2@oKZm1RN_~`7k%kR>1zK_h`#JsyZr& zUZ$2?juU@UA0a)ebQ5$0^2>$RbMKS>;TIf)cM<9_8PjIwuUuX{Gm?Ds$1ctCFpEL8 zR?nn$Z{`FPy3oC{6O(eSXt@*|od9VAf*Qs-J=0R(GcQ9cyoI%cSS@~<~^{u1h-Ar9KX5z z*-H8Ka-Z(EUm8_7Q~=f@{T}D6KcAVMQcFR-6q*mRn4h07G;C$Oe{+SsF(C#-Az3fd z&uzsL#+$4%c%j5VBeI-FH!~URt&SeST&pEf6J`zfn!A;O#6%jRo*<5dN7Gt~j|rRJ zEasNg@v!LFSQ%?mimpH>rcLwHhKFXHf+`pi4 z7lgQRF72T+-<4IhEUkdDYpEu=u17beRQvVTJ0dsym)bPknGndw>R;x0B0Pf}($&Wf zbbSeOeoH;kS>dfiMoI9?BPcJ8{LYgl@VfEMT zk77{$9aY+08IFnuHJDq)<`S|3@|H7E-Ff1?`JG5T!W92gZQkdr8ia=`Yx|XnV+EnI zGVCFg0AkSUdz+>P7uB4uZd#FC&mo0(7a##Xf4+CpDr5Kh11S9XICZ!#rz9Go;VB+i;ujc6%&qc>WjF%72}x}Fo#e3RvdRESN%a9RS1TSdAZn<*E2 z$P3>N8P~x=Afo(xyy|4z@nM@oRm)cCTd-CS*&M$YD=hyvEIjvHdHJQGay<#+sB-5m zv4f%-0-ngnfNiB@GLhY=0S%DfWyZqJi%-GddZuUN`dd~n9Pt5>{-mp*uBfh|@*g$h z&2=0p9go*dyDv+vckgFV-=12REp+^uDt7ZHUy=cWN8{*r5)%_^OecCbaV3-2*3OjTKPu+bMUx+|dI3uQ^lYGPop8GG%hqPE9uwMjby}h3T<0RrObgdaL=$MSivD;*I4Yv6Tu6e;vMjh;{yvClHnl{tG!qVw5 z7i|McyzFo0y{B9V0FVu+f9LvM6X8V4WDCRJvl833;rvn4P>7Y^;Zt56nd7m?8iA)f zXUME_kQ@ay_`in#^*o^ETGX}q0U01i5WjUZNn1`(%GH%WcqNSaD*E2M`71-BSWmVF zH|>)W4j~@6pdXgbGDs0h#1k(F{ddaHQChmyoMxFj#q{1fIWKL2K0;+D+m`3Bd3-Nb z_B1tEULDq6P*AW})1UWGWAmk8oyPi%qQ>4osS2M)2B=Z_gu3Bq`BYxO)@E#8fD=Rg zV18Vl&y1shYx`;{1@x^Szw=Go{fkx`{Z|zbSX=|z3coXzWmIn_P~z}iRIn4n6dToJx-<1D+Gx>rfFabaJQclk5XI?+p8z3M3MDrvCY5g z_ckZu=;$b`-l)+I^_B!LpC%uNsY3v?q<7DC?%g}l9S%A4EDVt$WSAGW|V-!*g%eG8_~hhF%NwYcVP9 zA^O=vZFuTXz?iIkL&)Kxnq}Rq1GTi{y?GX01@A4%2^}R*9=b`0XP;TwkftUvD2B-p z@6?Sd68%5Q0~OSxom#s*A$<)~s4Mr98Ep6n!vmU{%T#j5)pWY@i;POJ#J0=Bp%E}) z(bMYIc81Ha(? zV9pbzV&s&gA8=1xYVFSPGgJiatDC>Ia)ZxkL~RPuQPv(vo97AYn=e8Qwvol06hYg8 ze1$ZyWP}N~2D}2U9CQtWRL0E!Z%XJLu~*x4=BTS~4xyRd_bhHf%y{Jhce4JUbF;T- zD&S|jbjnDwbu{j}o0FvRGP91982G6Hd*y5qd(%xz`nq;kngH_Uw6+BlZpEerRbLZE z%qr!f$y7S!(kvzxjH5G;{2tT%y}~V2we2=MRBqh7L!`o&;WgM0=fJ`&WHRP=D&r8y z)1c#c1wPB$FuVw5LPGNf&+t@sSA?iQgN^{wsN}O~lhl?{F;!$b-p5Bk*kX4SE)+EB zTK!Hz&6@|Rfjm7oud?zOxEF~0H{O;fXw|+{g)r&YXdNuwN^lrXuh~1_uDW1`%&4&k z2i=1BK92A$H-eK-ZDNe}bDVLW8ePrgj^_IGdFJ;o^~1 zu=kHFS&}=+M%vSBtte@SCi;Vrv_v^<$oNyfW*>op%VJk6?(fS8bjej;lz<(cxS~rU%k_JL6knR|Bs=Jn65dhAzHl3$LzW{)+H` zgIY`YlwH3S_SiAlVz=)=xyNeG7ps6}3qBU*UV2(fw!8gI2yUk!&Iq;n>f30?yZa=L zP41S8vMD?X-LsgG%xF$00FZz=l3zNz)a_?|S&|gpJT~~)Hk;nvZmR)?EdJQAf5CSX zyd{+h522+BzUB*K&&b25@UlLB6KRq;WWgq?b5CeFA;*6>M8j(DByL611o?>b(uuE~ zE53Sa!`(Z43y%dM^CbUaom1tDLzU&wPf<$_^NLck4@{a>Tr6XrZXEC{Qtw!(NiR&U=n32sfY3z(cQ_I&N(Bo=?`|lE<~QC^b`! z+D{Ia?qP4K{F!1}Zu89now?n8u6XG--OAh*Qu~ItCK54W_m=7<-ot`^b&ii84heT0 zf5RGZC&(YFk6aov2L0<}acj>t=R47Gd{Q70kvwb$F^&u4VT|e2MM#)B-nJ!lXYD69 zH~$Ulb)RqC67miUaYE6sGqA|}FK_5$I@$jLeC-Vhd#?UEnM}-`wE}V=Yy8T{W||D< zD0|w}?-nzqhw(o^m1IK`Pm?5tg{w^Yk+K=XR_qarU7YH_ndv;CiE1*Hk7x!0*B`By zZ*Y2jtVljtB0|UrPwj@-N_GcMCzp6ay8s$G&tWsSmk!3_+wX>iIu3u3hu*Z3o9VDh z?Nt*{DJ|`H+~o|!mRtp>!UQ-S37wUf+We2!gu!e>o@m9R$V$r~d`LrGkqBe_)iZxk z%n8D}SayV%-)>x$TXEB36}SyU{MqR7K14MHwwL>=!4|fO(!h8xUy@FMus`sLqWg{C z_A+R#_60z-Anp->)!knVfHF(>80+EDVWx~gw;fTWDo5?O;6*+Spxz?~(??}8C8no8 z?P0Nvn=sSd9*@7_j$HEosP3Lz^Pm5<{`LZk+|Q8ljP2D#BBY`9!bo}SWQjrXwfzc8 zZtrBx94lXFI0h(xfEgbB7XQ;NUH)jEC-BZx>X*hx}WNI*sPd zHM#65pB@$6z6C9(eg_i=3S_dvy)c1rt(CToJuMAF?&a)}q#dIRj>2aiLU!U%sgBFT zwgfQ#Enf6o^tZ^tbuiq8m(C$ZI?;1@Gki^P8B#_K{s(kE5z_m*jH;Z|(c^$)>q#Z8&u+c_I z&MFfL6{zWQjzIh3dlsWS&(n<2C~Ik1-_uku7U3Vd|5qgk>DDTfRM+rWOP!gG&gS+f}zxQFdO`{f(ok#RFCXu`xc(&6+Cks@uTd+`v8flY)AxcRT})DQ`I6 z?Ou$m$oG)|*$wEcblouL6>CX@$ekz5&Qw&rax4VOWRS049!G8AU0qNgVt5ezkv}P) zKOahc@Z$T}f8uDX+&Y+MR4r#pCd+iq4mnc5cHFdYPL?rRhF*a!fL?u}r)#YA;PLRQ zadVwaY1eTuh95ju?^t|ucDZnM-FRC-@q!lurtcUaS-@xI9^ChhF7gni^$aDB4t~4Z zfD@E>3=Ug$L@m27NN`JFIBn z*5PtaKB7%IE9(&3RyP2qzC!7zjV+qL-NG|4+wQyA3w`6#+HJ0*;O#UXI5oZAXZP|< z1XNdlfZ9R}XphJXJh7lrOyDWCZI{I4~VAtOtC{LeHsI7=fs_hon$(ntV`ZBWE+l~p}}B-(6i`C^^j9j8BHgQehF zEHdp7{(_3d+V$|bko9he~N~?0@Ru2ZBhB#gDsXtHsDp3@Y5GyioN|_^pC!D z`E_Rp`0_h}s+OQwAG=$jpmZpb|847`?UAYQ@O9Q+)Z`yE;phhx{P2YdNIpLN` z4OD}EUMbjN1!}OW5-drVem{)S96i508Wr!5+54B3R`u^vq41ayS{+YPAzCBBvlr5p zypa3-@1(KNf}z3yi0N}db09P;d8AoXwJxJtuaBo4tlHTKqwQ4EO$w7|K5Fus17oPn zYj=9eKkogz{dvJ?X2u)4y}T*~Y@4SqldRXb>s8@UQNVZz=>X}zn>+RWLJ zRY}6~ziD*8P{+=APEK9USH2msc=4R_x*ViuRyG3ErOB50?$`949$-#`mJZZ>&lwUr z{jt-sEOOhIO#`NctPt_K!`&B$9CJ?9pluk#m?pS$EgAcOZ+hziNQ-X`%C~4r^!OXD zN2D-jpi&}UcX9^qgOFVH*^k5!A_zVsnF{+QxKh z3fVa1f6?9$wba_NI};;42MnkT!M)}SRK=|DJ~+S{L@FM^r{?N^d&$4KdGLLN8LcB{-Ft0=$AVG36~t=_F9MF>Sg`UHxKps z&+1Gtb$MyT4;$uucWBuuV=NXjXHD#K-*e(oHOA+HgTr=-o)%4{@)4i&YqAp%R)EW` z({_u<7VRx8BzPY;W4D5l+o^us0Rm7+H^Jsip(dCxN9k>k`zociw(Cd6M3P=$otG!XY)KWfLX6t9=_N~5f&b4m)WK>72Rxn?T{_ig<8sv87a>Tq_y zH+^keTXwN^7XtqTuon7+$t3hUVjjg7x{7B7^Ja9FWyV#hXGiN(sO+owmVRcobVD#0TIbkNv4- zy5)fDDcTxX?UGs?SwVk5Vud3eU`YO!O!S9_=Mx-k zbv@UwS8KqeDX!e;2Gfd^BLdhV^w3Fo#W+xnF`GC8!y(ztql$rfH{Q1dYDU?7FE@|q zbmV`|%gRuyn1>l`-pFZF72LZ7Z{eW`2QZt;dUxw+9OUcOoM z91ci9|B!uz=`7!hX3p1|u{${|ihH;1xTN)@$FuT^h>Rp~T|qn?XZi7&2O%RVLhUs_ z-Wa?AO^GOIpxP?Ce{+(03tTuWGemySS`I<8V?4mw_wQTcoqwltSVb&yWP%k<@NyAk z$!lCR9dA4a-10}h%*_*(Zfz&17d$CE`kQunA*XXj=9GR5vQv>66R^1mga(XEmGc3M zNy~|2u{K*rKYJj3J(VLv+o{<}IcPrfF1_D@kwc-AhzL=9o_n)a4P${*MA`I|Ok<%x z?-Fx(jp@kk{;p6zM&HU5?$X}DAXBDE@sE^!Wr8_RpF{Jfm5W52PxVCu-Z=XO^Gg2$$=+56f& zD{}z$#kX=S=8aGO>_gxWYuTSeIv|W_I9Ojikkl^6k?P$WW`tG$=Z3mU8q$tmPmOAw zMQD;xk#gOFwUWvp8WM_w05cnJ9kP`3gTl7_Mgv8fsMKfO_+m zM454;-gpwPuI%;SaOwc}8v?5H4e)fxHm$8;*2%xN+jx;A*;iz51^g~5q%J5}B#$F} znl3y*eY>L|*|K+dJB4hk0X>3ZdaG@y6^NbopQ3w^k9h6iQk-p+ax;a=Af9?Y?|a#l z?YS42`e5i!Dkc3gTbzsbcmzc%?%m_;m*JF@+Hz0t&?ZIn?s89Ta%^g8>We+))GB&R zmqg(xPD;5;L&y1e{4b~QC%h!^7!Hr=A0`&_0NvV?<5ckPo%bWfx)4C)PcdX($3wHq zwVYMo(K8n-tb&kLC4>~6161+K@}{{8W= zV>RKiOm$$W+QZ&;gabgCQAOzyO6S(cdvKQ&(+!td-$oxVuNRYZa}TiJ zm*c4`P0kocKfVe_^b-!d_8l!KDV8skTciKmRHQ@v*)#n7hdaWeXHVzo?N6zYScX8dJyA>}m}dUGtGPa1*O_`p9srb{&Q~<*UY&fp7pwFJl((dEhStp+oSw7czw^{7wEj`|H0wjg zw+$0z8bXhClnh{Cx{N)~jL|4>}B#%gpVIU~_R+j?&MtXu0z!@qm^ z-YC2}J~^p!7r9eGU|G6FRjvzj-|GnLf3YlXmnP=s7Y6>rPrv+U}+owr~IdMa6&r7l4!$4$$OjhcE11v8AS$4d-(| zfAfOm?C_M$C93jfh~=ijWas9WT(G5Df9!&bQzn`}Vh*>TDv0Fj{xQ5H4Y>^=O(Ao7 z0E3MFv|&r4x%(wmU-iKX0Hu_O0=loMNp z4I~n2NjA-ElB>=o><}nX? zxAeiqk6p7H%WE6!T!Y3g2sC8`oHIw-j>24J@Y=# zf0b7*gGuZwypMDA>|=$iskJ}8&E)0f-4uz@zq5m{UPP^YS)sXJ@uGHQr=e?M@vw5* ziCY$-erb7}Q*`(Ck^Z;#MV|EjerZ18dIS% zz#hPCR6w_3;q>n6sqs#?rc;LQVYS^g zJeQChctknB<}JbOEy8)F{$8{Lk*&rX0DP*l4hK>O2g5QFkI?Y)AiGl8TPXA{5Z;O2 zJkn62lE7me_U-gz7wnrNa^E8d=dg7zTd!KjU1iAw1n9!~tv2MuwL<(K%)uu82r|tm zGjV;kT28y!uIEu`R5YtVF1cG|1YnU?nRSGIk|kl9zqNg@-Jvqg=!7&?u?I|iYliG^ zvaJOa8C^^qe!n{f_3-efnJT#wah8p&bJ8>X{HN1CY-`~Adr^KroZ}tLO!htCwFZSZ zq}N4^4%OBk`}nEmM?ZIS-@embte%?KftJ3Pgu%v1c?I#|NFuGWWMe$#@TDjY zBQ*?7Bo^&glARYhNq9khIQ|`tn_05E$++UABN%4%KL49!eevq3q$C^0={v%6S#74J zEciAST*M@Z7c5~!VF46}@pTnIBV`p@X#ow&4Z-HP>o5bCf5l>73r8BHq$g8tx5*_X zbzLw@F}E(~JQd zhEXjmSD$=Yx%1fNVGZM)5w3j;U51~DCH)YeD}hkPgp4r6Tuwvecd?^eFtOgC>io2v zr=nQF|32Q{8k@#%I?cNQe>Al6YzazJK5R+&1`s)nLGRpbxNgakx&`zT1oJnODT<6^15BRJ8bCsMBuKq zfw5r~JQwC~E98-dI2f?3eihx@%V{1T6# zD(6fd&&FO?DL?ztB{Q<6AimRGf|=%RCmEY5drKYK zOsuWsb%Q3dE8l2K`Y|}#bG$QGhSE4r*K;vArUM*Eq#Dui8DfttJ6l|{EGLmSaZtot z*WQoZqC*!ewqE19Wc|$-fGr=p?YI?w{mPGLanFlEWe(AgRr{Wg&nWM^AFFn0R#LFap za%0>Vp0Di=pKzyMZennpA{1a|%O{88>`=C+7qW5Nx&gVVm_DI>>NN@A4Png8d?edf zpiwBS$HM}%g;O#{Hil-9EjZ|hq;v)dXqS5Bxfm#vtUoVryEV1VrO}CGj%2&eg5N^G zs}-kIpXl4pFpd$oXk@VyZuv^SZ@l2kq2Zc6k|r?f!eI^vm^? zxpBelY)@{@`LW|N5QT|_F;D;TV*(FucR;yu%`vuK5T5{jT(3Sj$@q%k5Dnd{gI67A z`0Om9P02wvo2?AQR#jESk$qzF*z7-j@v4v@dE+3K$z64DGsa}VkKz&9ab>Q1C z{Kh^h-jm3vtSO2-BQ7wRV&=m$K{6?DSuyjc^+*zXg|~QB@SHkNNvWb}GMON0!24x{ zI$$0y=sf3~l6?G;{h`nti&zK$?LliL9&64e?yJ#2tf=a3cjW}QM|^A3(-C<|VCXu2 zRSE5JJ*Hy!gqH4=w$6uid1DW}22fetj5I=Mvo z+}3;1)z5F;m|;uT;1rjST!k@qt;?v=N^W$`+$DPAQS~611f;3yQdu4y8444T_-p%z|IC*5 zE{PfuuiB{9&I~wrZ@XLpRZ>Fq|sbj_Cl>{$pil_2l-RDM?T5aVnLB zZhwzZ(JoR920&m=m)~*lLx|39vjB|wEVLIf6?7t5aZ7HjF z7lhY4LsX}}(TChab z10fF+8!NJ$bzPcpF3Y?Gm^2h9E-Y6Zc%rE}wwaC+173!Y@BJu}Zp6gQsP5~2S9>XG z3hrFM{-RYmWAJTsu`CdeZtvHTxSG-9?9);+?2pB^~)y=|Cx*g%BkqJG@5 zt?%nOuJ7Yn*rXM4x`8w6zDeVRq+o%YPfyrYy??)|>gt2)zhCBjs*!C-JrO_q{<6yj zulfIc{r>6tv+Yi1|5#Tuup~3zVDNZ0!`~zOt?~R>^){#DXXm#_Hd@_aW8hbq&(6TV zpgEV}1H-$AHVuNum|rAFGuSlL$N>qyZHxz)ceHE(9P ziEazPs!luyDg|lnluMYy_Wp6L+yRcqj3pa*8RQ)7_<;obZH5PocMexFDEF{mIB3RD z!SK@tNXX_eH?SA*KWFSQfU0@&8K@Abu|d{$I9M^)g0b(x_la}=ns)(XoWax8&t;uc GLK6VN{n@wx literal 0 HcmV?d00001 diff --git a/repos/gems/src/app/backdrop/main.cc b/repos/gems/src/app/backdrop/main.cc new file mode 100644 index 0000000000..6a9bd4c8e9 --- /dev/null +++ b/repos/gems/src/app/backdrop/main.cc @@ -0,0 +1,375 @@ +/* + * \brief Backdrop for Nitpicker + * \author Norman Feske + * \date 2009-08-28 + */ + +/* + * Copyright (C) 2009-2014 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local includes */ +#include "png_image.h" +#include "file.h" +#include "xml_anchor.h" +#include "texture_utils.h" + +using namespace Genode; + + +namespace Backdrop { struct Main; } + + +struct Backdrop::Main +{ + Nitpicker::Connection nitpicker; + + struct Buffer + { + Nitpicker::Connection &nitpicker; + + /* physical screen size */ + Framebuffer::Mode const mode = nitpicker.mode(); + + /** + * Return dataspace capability for virtual framebuffer + */ + Dataspace_capability _ds_cap(Nitpicker::Connection &nitpicker) + { + /* setup virtual framebuffer mode */ + nitpicker.buffer(mode, false); + + if (mode.format() != Framebuffer::Mode::RGB565) { + PWRN("Color mode %d not supported\n", (int)mode.format()); + return Dataspace_capability(); + } + + return nitpicker.framebuffer()->dataspace(); + } + + Attached_dataspace fb_ds { _ds_cap(nitpicker) }; + + size_t surface_num_bytes() const + { + return size().count()*mode.bytes_per_pixel(); + } + + Attached_ram_dataspace surface_ds { env()->ram_session(), surface_num_bytes() }; + + /** + * Constructor + */ + Buffer(Nitpicker::Connection &nitpicker) : nitpicker(nitpicker) { } + + /** + * Return size of virtual framebuffer + */ + Surface_base::Area size() const + { + return Surface_base::Area(mode.width(), mode.height()); + } + + /** + * Return back buffer as painting surface + */ + template + Surface surface() + { + return Surface(surface_ds.local_addr(), size()); + } + + void flush_surface() + { + /* blit back to front buffer */ + blit(surface_ds.local_addr(), surface_num_bytes(), + fb_ds.local_addr(), surface_num_bytes(), surface_num_bytes(), 1); + } + }; + + Lazy_volatile_object buffer; + + Nitpicker::Session::View_handle view_handle = nitpicker.create_view(); + + void _update_view() + { + /* display view behind all others */ + typedef Nitpicker::Session::Command Command; + nitpicker.enqueue(view_handle); + Nitpicker::Rect rect(Nitpicker::Point(), buffer->size()); + nitpicker.enqueue(view_handle, rect); + nitpicker.enqueue(view_handle); + nitpicker.execute(); + } + + Signal_receiver &sig_rec; + + /** + * Function called on config change or mode change + */ + void handle_config(unsigned); + + Signal_dispatcher
config_dispatcher = { + sig_rec, *this, &Main::handle_config}; + + void handle_sync(unsigned); + + Signal_dispatcher
sync_dispatcher = { + sig_rec, *this, &Main::handle_sync}; + + template + void paint_texture(Surface &, Texture const &, Surface_base::Point, bool); + + void apply_image(Xml_node); + void apply_fill(Xml_node); + + Main(Signal_receiver &sig_rec) : sig_rec(sig_rec) + { + /* trigger application of initial config */ + Signal_transmitter(config_dispatcher).submit(); + + nitpicker.mode_sigh(config_dispatcher); + } +}; + + +/** + * Calculate designated image size with proportional scaling applied + */ +static Surface_base::Area calc_scaled_size(Xml_node operation, + Surface_base::Area image_size, + Surface_base::Area mode_size) +{ + char const *attr = "scale"; + if (!operation.has_attribute(attr)) + return image_size; + + /* prevent division by zero, below */ + if (image_size.count() == 0) + return image_size; + + /* + * Determine scale ratio (in 16.16 fixpoint format) + */ + unsigned const ratio = + operation.attribute(attr).has_value("fit") ? + min((mode_size.w() << 16) / image_size.w(), + (mode_size.h() << 16) / image_size.h()) : + operation.attribute(attr).has_value("zoom") ? + max((mode_size.w() << 16) / image_size.w(), + (mode_size.h() << 16) / image_size.h()) : + 1 << 16; + + /* + * We add 0.5 (1 << 15) to round instead of truncating the fractional + * part when converting the fixpoint numbers to integers. + */ + return Surface_base::Area((image_size.w()*ratio + (1 << 15)) >> 16, + (image_size.h()*ratio + (1 << 15)) >> 16); +} + + +template +void Backdrop::Main::paint_texture(Surface &surface, Texture const &texture, + Surface_base::Point pos, bool tiled) +{ + /* prevent division by zero */ + if (texture.size().count() == 0) + return; + + if (tiled) { + + /* shortcuts */ + int const w = texture.size().w(), surface_w = surface.size().w(); + int const h = texture.size().h(), surface_h = surface.size().h(); + + /* draw tiles across the whole surface */ + for (int y = (pos.y() % h) - h; y < surface_h + h; y += h) + for (int x = (pos.x() % w) - w; x < surface_w + w; x += w) + Texture_painter::paint(surface, texture, Color(), + Texture_painter::Point(x, y), + Texture_painter::SOLID, + true); + + } else { + + Texture_painter::paint(surface, texture, Color(), pos, + Texture_painter::SOLID, true); + } +} + + +void Backdrop::Main::apply_image(Xml_node operation) +{ + typedef Surface_base::Point Point; + typedef Surface_base::Area Area; + + if (!operation.has_attribute("png")) { + PWRN("missing 'png' attribute in node"); + return; + } + + char png_file_name[256]; + png_file_name[0] = 0; + operation.attribute("png").value(png_file_name, sizeof(png_file_name)); + + File file(png_file_name, *env()->heap()); + + Anchor anchor(operation); + + Png_image png_image(file.data()); + + Area const scaled_size = calc_scaled_size(operation, png_image.size(), + Area(buffer->mode.width(), + buffer->mode.height())); + /* + * Determine parameters of graphics operation + */ + int const h_gap = (int)buffer->mode.width() - scaled_size.w(), + v_gap = (int)buffer->mode.height() - scaled_size.h(); + + int const anchored_xpos = anchor.horizontal == Anchor::LOW ? 0 + : anchor.horizontal == Anchor::CENTER ? h_gap/2 + : anchor.horizontal == Anchor::HIGH ? h_gap + : 0; + + int const anchored_ypos = anchor.vertical == Anchor::LOW ? 0 + : anchor.vertical == Anchor::CENTER ? v_gap/2 + : anchor.vertical == Anchor::HIGH ? v_gap + : 0; + + Point const offset = Decorator::point_attribute(operation); + + Point const pos = Point(anchored_xpos, anchored_ypos) + offset; + + bool const tiled = operation.has_attribute("tiled") + && operation.attribute("tiled").has_value("yes"); + + unsigned alpha = Decorator::attribute(operation, "alpha", 256U); + + /* obtain texture containing the pixels of the PNG image */ + Texture *png_texture = png_image.texture(); + + /* create texture with the scaled image */ + Chunky_texture scaled_texture(*env()->ram_session(), scaled_size); + scale(*png_texture, scaled_texture); + + png_image.release_texture(png_texture); + + /* + * Code specific for the screen mode's pixel format + */ + + /* create texture with down-sampled scaled image */ + typedef Pixel_rgb565 PT; + Chunky_texture texture(*env()->ram_session(), scaled_size); + convert_pixel_format(scaled_texture, texture, alpha); + + /* paint texture onto surface */ + Surface surface = buffer->surface(); + paint_texture(surface, texture, pos, tiled); +} + + +void Backdrop::Main::apply_fill(Xml_node operation) +{ + /* + * Code specific for the screen mode's pixel format + */ + + /* create texture with down-sampled scaled image */ + typedef Pixel_rgb565 PT; + + Surface surface = buffer->surface(); + + Color const color = Decorator::attribute(operation, "color", Color(0, 0, 0)); + + Box_painter::paint(surface, Surface_base::Rect(Surface_base::Point(0, 0), + buffer->size()), color); +} + + +void Backdrop::Main::handle_config(unsigned) +{ + config()->reload(); + + buffer.construct(nitpicker); + + /* clear surface */ + apply_fill(Xml_node("")); + + /* apply graphics primitives defined in the config */ + try { + for (unsigned i = 0; i < config()->xml_node().num_sub_nodes(); i++) { + try { + Xml_node operation = config()->xml_node().sub_node(i); + + if (operation.has_type("image")) + apply_image(operation); + + if (operation.has_type("fill")) + apply_fill(operation); + } + catch (...) { + /* + * Ignore failure of individual operation, i.e., non-existing + * files or malformed PNG data. + */ + } + } + } catch (...) { /* ignore failure to obtain config */ } + + /* schedule buffer refresh */ + nitpicker.framebuffer()->sync_sigh(sync_dispatcher); +} + + +void Backdrop::Main::handle_sync(unsigned) +{ + buffer->flush_surface(); + _update_view(); + + /* disable sync signal until the next call of 'handle_config' */ + nitpicker.framebuffer()->sync_sigh(Signal_context_capability()); +} + + +/* + * Silence debug messages + */ +extern "C" void _sigprocmask() { } + + +int main(int argc, char **argv) +{ + static Signal_receiver sig_rec; + + static Backdrop::Main application(sig_rec); + + /* process incoming signals */ + for (;;) { + using namespace Genode; + + Signal sig = sig_rec.wait_for_signal(); + Signal_dispatcher_base *dispatcher = + dynamic_cast(sig.context()); + + if (dispatcher) + dispatcher->dispatch(sig.num()); + } +} diff --git a/repos/gems/src/app/backdrop/png_image.h b/repos/gems/src/app/backdrop/png_image.h new file mode 100644 index 0000000000..4a9900269a --- /dev/null +++ b/repos/gems/src/app/backdrop/png_image.h @@ -0,0 +1,181 @@ +/* + * \brief Utility for reading PNG images + * \author Norman Feske + * \date 2014-08-14 + */ + +/* + * Copyright (C) 2014 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _PNG_IMAGE_H_ +#define _PNG_IMAGE_H_ + +/* Genode includes */ +#include + +/* libpng include */ +#include + +/* local includes */ +#include "chunky_texture.h" +#include "texture_utils.h" + +class Png_image +{ + public: + + /** + * Exception types + */ + class Read_struct_failed { }; + class Info_failed { }; + + private: + + template + static T _assert_non_null(T && arg) + { + if (!arg) + throw EXC(); + + return arg; + }; + + struct Read_struct + { + /* start of PNG data */ + png_bytep const data; + + /* read position, maintained by 'read_callback' */ + unsigned pos = 0; + + static void callback(png_structp png_ptr, png_bytep dst, png_size_t len) + { + Png_image *png = (Png_image *)png_get_io_ptr(png_ptr); + Genode::memcpy(dst, png->_read_struct.data + png->_read_struct.pos, len); + png->_read_struct.pos += len; + } + + png_structp png_ptr = + _assert_non_null( + png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0)); + + Read_struct(void *data) : data((png_bytep)data) + { + png_set_read_fn(png_ptr, this, callback); + } + + ~Read_struct() + { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + } + }; + + Read_struct _read_struct; + + struct Info + { + png_structp png_ptr; + png_infop info_ptr; + + int bit_depth, color_type, interlace_type; + png_uint_32 img_w, img_h; + + Info(png_structp png_ptr) + : + png_ptr(png_ptr), + info_ptr(_assert_non_null(png_create_info_struct(png_ptr))) + { + png_read_info(png_ptr, info_ptr); + + png_get_IHDR(png_ptr, info_ptr, &img_w, &img_h, &bit_depth, &color_type, + &interlace_type, nullptr, nullptr); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + if (bit_depth < 8) png_set_packing(png_ptr); + if (bit_depth == 16) png_set_strip_16(png_ptr); + } + + ~Info() + { + png_destroy_info_struct(png_ptr, &info_ptr); + } + + } _info { _read_struct.png_ptr }; + + struct Row + { + size_t const row_num_bytes; + png_bytep const row_ptr; + + Row(png_structp png_ptr, png_infop info_ptr) + : + row_num_bytes(png_get_rowbytes(png_ptr, info_ptr)*8), + row_ptr((png_bytep)Genode::env()->heap()->alloc(row_num_bytes)) + { } + + ~Row() + { + Genode::env()->heap()->free(row_ptr, row_num_bytes); + } + } _row { _read_struct.png_ptr, _info.info_ptr }; + + public: + + /** + * Constructor + * + * \throw Read_struct_failed + * \throw Info_failed + */ + Png_image(void *data) : _read_struct(data) { } + + /** + * Return size of PNG image + */ + Genode::Surface_base::Area size() const + { + return Genode::Surface_base::Area(_info.img_w, _info.img_h); + } + + /** + * Obtain PNG image as texture + */ + template + Genode::Texture *texture() + { + Genode::Texture *texture = new (Genode::env()->heap()) + Chunky_texture(*Genode::env()->ram_session(), size()); + + /* fill texture with PNG image data */ + for (unsigned i = 0; i < size().h(); i++) { + png_read_row(_read_struct.png_ptr, _row.row_ptr, NULL); + texture->rgba((unsigned char *)_row.row_ptr, size().w()*4, i); + } + + return texture; + } + + /** + * Free texture obtained via 'texture()' + */ + template + void release_texture(Genode::Texture *texture) + { + Chunky_texture *chunky_texture = + static_cast *>(texture); + + Genode::destroy(Genode::env()->heap(), chunky_texture); + } +}; + +#endif /* _PNG_IMAGE_H_ */ diff --git a/repos/gems/src/app/backdrop/target.mk b/repos/gems/src/app/backdrop/target.mk new file mode 100644 index 0000000000..c7a61f9411 --- /dev/null +++ b/repos/gems/src/app/backdrop/target.mk @@ -0,0 +1,3 @@ +TARGET = backdrop +SRC_CC = main.cc file.cc texture.cc +LIBS = base config libc libpng zlib blit diff --git a/repos/gems/src/app/backdrop/texture.cc b/repos/gems/src/app/backdrop/texture.cc new file mode 100644 index 0000000000..0ecdf26b5c --- /dev/null +++ b/repos/gems/src/app/backdrop/texture.cc @@ -0,0 +1,79 @@ +/* + * \brief Support for Genode::Texture + * \author Norman Feske + * \date 2014-08-14 + */ + +/* + * Copyright (C) 2014 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include + +namespace Genode { + + template <> + void + Texture::rgba(unsigned char const *rgba, unsigned len, int y) + { + if (len > size().w()) len = size().w(); + if (y < 0 || y >= (int)size().h()) return; + + Pixel_rgb565 *dst_pixel = pixel() + y*size().w(); + unsigned char *dst_alpha = alpha() ? alpha() + y*size().w() : 0; + + Dither_matrix::Row dither_row = Dither_matrix::row(y); + + for (unsigned i = 0; i < len; i++) { + + int v = dither_row.value(i) >> 5; + int r = *rgba++ + v; + int g = *rgba++ + v; + int b = *rgba++ + v; + int a = *rgba++; + + if (a) a += v; + + dst_pixel[i].rgba(min(r, 255), min(g, 255), min(b, 255)); + + if (dst_alpha) + dst_alpha[i] = min(a, 255); + } + } + + + template <> + void + Texture::rgba(unsigned char const *rgba, unsigned len, int y) + { + if (len > size().w()) len = size().w(); + if (y < 0 || y >= (int)size().h()) return; + + Pixel_rgb888 *dst_pixel = pixel() + y*size().w(); + unsigned char *dst_alpha = alpha() ? alpha() + y*size().w() : 0; + + for (unsigned i = 0; i < len; i++) { + + int r = *rgba++; + int g = *rgba++; + int b = *rgba++; + int a = *rgba++; + + dst_pixel[i].rgba(r, g, b); + + if (dst_alpha) + dst_alpha[i] = min(a, 255); + } + } +} + + +template class Genode::Texture; +template class Genode::Texture; diff --git a/repos/gems/src/app/backdrop/texture_utils.h b/repos/gems/src/app/backdrop/texture_utils.h new file mode 100644 index 0000000000..e8fbf1159c --- /dev/null +++ b/repos/gems/src/app/backdrop/texture_utils.h @@ -0,0 +1,97 @@ +/* + * \brief Utilities for working with textures + * \author Norman Feske + * \date 2014-08-21 + */ + +/* + * Copyright (C) 2014 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _TEXTURE_UTILS_H_ +#define _TEXTURE_UTILS_H_ + +#include + +template +static void scale(Genode::Texture const &src, Genode::Texture &dst) +{ + /* sanity check to prevent division by zero */ + if (dst.size().count() == 0) + return; + + Genode::size_t const row_num_bytes = dst.size().w()*4; + unsigned char *row = (unsigned char *)Genode::env()->heap()->alloc(row_num_bytes); + + unsigned const mx = (src.size().w() << 16) / dst.size().w(); + unsigned const my = (src.size().h() << 16) / dst.size().h(); + + for (unsigned y = 0, src_y = 0; y < dst.size().h(); y++, src_y += my) { + + unsigned const src_line_offset = src.size().w()*(src_y >> 16); + + PT const *pixel_line = src.pixel() + src_line_offset; + unsigned char const *alpha_line = src.alpha() + src_line_offset; + + unsigned char *d = row; + for (unsigned x = 0, src_x = 0; x < dst.size().w(); x++, src_x += mx) { + + unsigned const pixel_offset = src_x >> 16; + + PT const pixel = pixel_line[pixel_offset]; + unsigned char const alpha = alpha_line[pixel_offset]; + + *d++ = pixel.r(); + *d++ = pixel.g(); + *d++ = pixel.b(); + *d++ = alpha; + } + + dst.rgba(row, dst.size().w(), y); + } + + Genode::env()->heap()->free(row, row_num_bytes); +} + + +template +static void convert_pixel_format(Genode::Texture const &src, + Genode::Texture &dst, + unsigned alpha) +{ + /* sanity check */ + if (src.size() != dst.size()) + return; + + Genode::size_t const row_num_bytes = dst.size().w()*4; + unsigned char *row = (unsigned char *)Genode::env()->heap()->alloc(row_num_bytes); + + /* shortcuts */ + unsigned const w = dst.size().w(), h = dst.size().h(); + + for (unsigned y = 0, line_offset = 0; y < h; y++, line_offset += w) { + + SRC_PT const *src_pixel = src.pixel() + line_offset; + unsigned char const *src_alpha = src.alpha() + line_offset; + + /* fill row buffer with values from source texture */ + unsigned char *d = row; + for (unsigned x = 0; x < w; x++, src_pixel++, src_alpha++) { + + *d++ = src_pixel->r(); + *d++ = src_pixel->g(); + *d++ = src_pixel->b(); + *d++ = (*src_alpha * alpha) >> 8; + } + + /* assign row to destination texture */ + dst.rgba(row, w, y); + } + + Genode::env()->heap()->free(row, row_num_bytes); +} + +#endif /* _TEXTURE_UTILS_H_ */ diff --git a/repos/gems/src/app/backdrop/xml_anchor.h b/repos/gems/src/app/backdrop/xml_anchor.h new file mode 100644 index 0000000000..2b42dbea60 --- /dev/null +++ b/repos/gems/src/app/backdrop/xml_anchor.h @@ -0,0 +1,71 @@ +/* + * \brief Utility for parsing an anchor attribute from an XML node + * \author Norman Feske + * \date 2014-08-21 + */ + +/* + * Copyright (C) 2014 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _XML_ANCHOR_H_ +#define _XML_ANCHOR_H_ + +#include + +class Anchor +{ + public: + + enum Direction { LOW, CENTER, HIGH }; + + Direction horizontal = CENTER, vertical = CENTER; + + private: + + typedef Genode::Xml_node Xml_node; + + struct Value + { + char const *value; + Direction horizontal, vertical; + }; + + Value const _values[10] = { { "top_left", LOW, LOW }, + { "top", CENTER, LOW }, + { "top_right", HIGH, LOW }, + { "left", LOW, CENTER }, + { "center", CENTER, CENTER }, + { "right", HIGH, CENTER }, + { "bottom_left", LOW, HIGH }, + { "bottom", CENTER, HIGH }, + { "bottom_right", HIGH, HIGH }, + { nullptr, CENTER, CENTER } }; + + public: + + Anchor(Xml_node node) + { + char const * const attr = "anchor"; + + if (!node.has_attribute(attr)) + return; + + Xml_node::Attribute const anchor = node.attribute(attr); + + for (Value const *value = _values; value->value; value++) { + if (anchor.has_value(value->value)) { + horizontal = value->horizontal; + vertical = value->vertical; + return; + } + } + + PWRN("unsupported anchor attribute value"); + } +}; + +#endif /* _XML_ANCHOR_H_ */