{"id":260,"date":"2016-12-20T00:01:21","date_gmt":"2016-12-20T00:01:21","guid":{"rendered":"http:\/\/www.piboxproject.com\/?p=260"},"modified":"2016-12-20T00:01:21","modified_gmt":"2016-12-20T00:01:21","slug":"pibox-piclock-themes-using-cairo","status":"publish","type":"post","link":"https:\/\/www.piboxproject.com\/index.php\/2016\/12\/20\/pibox-piclock-themes-using-cairo\/","title":{"rendered":"PiBox: PiClock themes using Cairo"},"content":{"rendered":"<div id=\"dslc-theme-content\"><div id=\"dslc-theme-content-inner\"><div id=\"attachment_5884\" style=\"width: 160px\" class=\"wp-caption alignleft\"><a href=\"http:\/\/www.graphics-muse.org\/wp\/?attachment_id=5884\" rel=\"attachment wp-att-5884\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5884\" class=\"wp-image-5884 size-thumbnail\" src=\"https:\/\/i0.wp.com\/www.graphics-muse.org\/wp\/wp-content\/uploads\/2016\/12\/piclock-notheme-150x150.png?resize=150%2C150\" width=\"150\" height=\"150\" \/><\/a><p id=\"caption-attachment-5884\" class=\"wp-caption-text\">The original clock was primitive, mostly because I didn&#8217;t understand how to use images and transformations correctly, even though I&#8217;d used them in the PiBox launcher!<\/p><\/div>\n<p>Having just completed work on a wifi scanner for <a href=\"https:\/\/gitlab.com\/pibox\/pibox-network-config\">PiBox Network Config<\/a>, I decided to expand on the lessons learned with Cairo to add themes to <a href=\"https:\/\/gitlab.com\/pibox\/piclock\">PiClock<\/a>.\u00a0 The original clock was a simple drawing of the clock face boundary with three hands.\u00a0 The biggest task there was to compute the end points of the lines drawn for each hand.\u00a0 Nothing complex there.<\/p>\n<p>But there is no reason custom clock face images and hands can&#8217;t be used.\u00a0 The trick is learning to work with <a href=\"https:\/\/cairographics.org\/\">Cairo<\/a> layers (which happens without extra coding) and discovering how to properly rotate and position the hands.\u00a0 Turns out none of this is hard, but it was hard finding out how to use the Cairo API.<\/p>\n<p>So the first thing to do for a theme is define what I wanted to display.\u00a0 There are five images you define for PiClock themes:\u00a0 <em>the clock face<\/em>, the <em>hour<\/em>, <em>minute<\/em> and <em>second<\/em> hands and an <em>overlay<\/em> image.\u00a0 The overlay allows you to add special effects like a glass cover.\u00a0 The theme needs a configuration file to specify these images.\u00a0 I chose to use a <a href=\"http:\/\/www.json.org\/\">JSON<\/a> format.\u00a0 Previously, PiBox apps used XML for configuration files but in the last year I&#8217;ve become a big fan of JSON due to its easy-to-read name\/value pair format.\u00a0 Also, I&#8217;ve found a very simple to use and lightweight JSON library called <a href=\"https:\/\/github.com\/kgabis\/parson\">Parson<\/a>.\u00a0 I&#8217;ve integrated this into <a href=\"https:\/\/gitlab.com\/pibox\/libpibox\">libpibox<\/a> so all apps can use it.<\/p>\n<p>The JSON format for PiClock themes is extremely simple.<\/p>\n<p><code>{ <\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0 \"theme\": {<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"face\":\"&lt;Name of clock face file&gt;\",<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"overlay\":\"&lt;Name of clock overlay file&gt;\",<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"hour\":\"&lt;Name of hour hand file&gt;\",<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"min\":\"&lt;Name of minute hand file&gt;\",<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"sec\":\"&lt;Name of second hand file&gt;\",<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"center\":{<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"hour\": { \"x\":n, \"y\":m },<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"min\": { \"x\":n, \"y\":m },<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"sec\": { \"x\":n, \"y\":m }<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 },<\/code><br \/>\n<code>\u00a0\u00a0\u00a0\u00a0 } <\/code><br \/>\n<code>}<\/code><\/p>\n<p>The clock face has a dimension limit of 512&#215;512 which the code enforces.\u00a0 After experimentation on the PiBox hardware I found it was best to make faces at that size and not smaller, but I don&#8217;t enforce that.\u00a0 The center object defines the rotation point within each of the hands images as it cannot be assumed the hand rotates around its real center.\u00a0 Other than that, the theme is self explanatory.<\/p>\n<p>Before I could parse the file I needed it to have a home.\u00a0 I use the -T option when testing PiBox apps which points the code at files within the source tree.\u00a0 In production the data files are stored under \/etc\/piclock\/themes.\u00a0 Since there can be more than one theme there needs to be a top level file stating the currently selected theme.<\/p>\n<p>Parsing the file requires a single line of Parson code, plus extracting a reference to access fields.<\/p>\n<p style=\"padding-left: 30px;\"><code>JSON_Value data = json_parse_file_with_comments(buf);<\/code><br \/>\n<code>obj = json_value_get_object(data);<\/code><\/p>\n<p>The data object is abstract.\u00a0 Fields can be extracted rather easily using the <em>dot<\/em> functions:<\/p>\n<p style=\"padding-left: 30px;\"><code>face = json_object_dotget_string(obj, \"theme.face\");<\/code><br \/>\n<code> hour = json_object_dotget_string(obj, \"theme.hour\");<\/code><br \/>\n<code> min = json_object_dotget_string(obj, \"theme.min\");<\/code><br \/>\n<code> sec = json_object_dotget_string(obj, \"theme.sec\");<\/code><br \/>\n<code> overlay = json_object_dotget_string(obj, \"theme.overlay\");<\/code><\/p>\n<p>This extracts the filenames, which must be prefixed with the installation directory.\u00a0 Following this is an in depth evaluation of the configuration to make sure files exist, can be read into Cairo surfaces and have reasonable sizes with centers properly defined.<\/p>\n<p>A clock theme is drawn by first drawing a black background (for any transparent regions in the images).\u00a0 The clock face is drawn over this.\u00a0 The surfaces for the face and hands are generated when the theme is read so that they can be reused every time the clock is updated.\u00a0 The hands are drawn over the face and the overlay over the hands.\u00a0 Those are the layers.\u00a0 As long as they are drawn in this order the layers are composited correctly.<\/p>\n<p>Now for the important trick:\u00a0 translation and rotation.\u00a0 What was difficult to discover the use of <a href=\"http:\/\/stackoverflow.com\/questions\/35995607\/gtk-cairo-load-png-rotate-and-copy-rectangle-to-surface\/35996791#35996791\">patterns and matrices<\/a> for the drawing operation.\u00a0 The pattern is effectively a copy of the original image surface.\u00a0 The pattern is first translated so its defined center is at the origin (0,0) at which point the rotation is applied.\u00a0 Then the pattern is moved to the center of the clock window.\u00a0 Obviously the face and overlay don&#8217;t need rotations, just the hands.\u00a0 Here is the generic handler for hands.<\/p>\n<p><code>void draw_hand(GtkWidget *widget, cairo_t *cr, gint hand)<br \/>\n{<br \/>\ncairo_status_t\u00a0 rc;<br \/>\ncairo_surface_t *image = NULL;<br \/>\ncairo_pattern_t *pattern;<br \/>\ngint\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 width, height;<br \/>\ngdouble\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 offset_x, offset_y;<br \/>\nGtkPiclock\u00a0\u00a0\u00a0\u00a0\u00a0 *piclock;<br \/>\nGtkRequisition\u00a0 req;<br \/>\ncairo_matrix_t\u00a0 matrix;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 piclock = GTK_PICLOCK(widget);<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 req.width = gdk_window_get_width(widget-&gt;window);<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 req.height = gdk_window_get_height(widget-&gt;window);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/*<br \/>\n* Setup based on the hand requested.<br \/>\n*\/<br \/>\nswitch( hand )<br \/>\n{<br \/>\ncase HAND_HOUR:<br \/>\nimage = piclock-&gt;theme-&gt;hour_sf;<br \/>\ndegrees = -1*(double)piclock-&gt;hoursHand\/60.0*360.0;<br \/>\noffset_x = (double)piclock-&gt;theme-&gt;hour_pt.x;<br \/>\noffset_y = (double)piclock-&gt;theme-&gt;hour_pt.y;<br \/>\nbreak;<br \/>\ncase HAND_MIN:<br \/>\nimage = piclock-&gt;theme-&gt;min_sf;<br \/>\ndegrees = -1*(double)piclock-&gt;minutesHand\/60.0*360.0;<br \/>\noffset_x = (double)piclock-&gt;theme-&gt;min_pt.x;<br \/>\noffset_y = (double)piclock-&gt;theme-&gt;min_pt.y;<br \/>\nbreak;<br \/>\ncase HAND_SEC:<br \/>\nimage = piclock-&gt;theme-&gt;sec_sf;<br \/>\ndegrees = -1*(double)piclock-&gt;secondsHand\/60.0*360.0;<br \/>\noffset_x = (double)piclock-&gt;theme-&gt;sec_pt.x;<br \/>\noffset_y = (double)piclock-&gt;theme-&gt;sec_pt.y;<br \/>\nbreak;<br \/>\n}<br \/>\nif ( image == NULL )<br \/>\nreturn;<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 width = cairo_image_surface_get_width(image);<br \/>\nheight = cairo_image_surface_get_height(image);<br \/>\npattern = cairo_pattern_create_for_surface (image);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/* translate the rotation point of the hand to the origin. *\/<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 cairo_matrix_init_identity (&amp;matrix);<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 cairo_matrix_translate (&amp;matrix, (double)offset_x, (double)offset_y);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/* Rotate to the correct clock position. *\/<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 cairo_matrix_rotate (&amp;matrix, deg2rad(degrees));<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 \/* Move to the center of the clock window *\/<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 cairo_matrix_translate (&amp;matrix,<\/code><br \/>\n<code> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -req.width\/2,<\/code><br \/>\n<code> \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 -req.height\/2);<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 cairo_pattern_set_matrix (pattern, &amp;matrix);<\/code><\/p>\n<p><code>\u00a0\u00a0\u00a0 cairo_set_source (cr, pattern);<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 cairo_pattern_destroy (pattern);<\/code><br \/>\n<code> \u00a0\u00a0\u00a0 cairo_paint(cr);<\/code><br \/>\n<code> }<\/code><\/p>\n<div id=\"attachment_5882\" style=\"width: 160px\" class=\"wp-caption alignleft\"><a href=\"http:\/\/www.graphics-muse.org\/wp\/?attachment_id=5882\" rel=\"attachment wp-att-5882\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5882\" class=\"wp-image-5882 size-thumbnail\" src=\"https:\/\/i0.wp.com\/www.graphics-muse.org\/wp\/wp-content\/uploads\/2016\/12\/piclock-test-150x150.png?resize=150%2C150\" width=\"150\" height=\"150\" \/><\/a><p id=\"caption-attachment-5882\" class=\"wp-caption-text\">The test theme has a reflective overlay appliedover the face and hands.<\/p><\/div>\n<p>After getting the window dimensions and accessing the previously created image surface, a pattern is created and a matrix initialized.\u00a0 Then the translation is done to move the configured center point to the origin.\u00a0 A rotation is applied and the pattern is moved to the center of the window.\u00a0 Once the pattern is set as a source for the main Cairo surface it can be destroyed and the main surface updated.<\/p>\n<p>There are now two themes for PiClock.\u00a0 The first is a test theme that includes the overlay.\u00a0 This isn&#8217;t as clean as I&#8217;d like &#8211; the overlay doesn&#8217;t quite look like a reflective surface to me.\u00a0 The overlay is made from a couple of white to transparent radial gradients made with <a href=\"https:\/\/www.gimp.org\/\">GIMP<\/a>.\u00a0 Cairo composites transparency quite nicely, as can be seen with the clock hands.<\/p>\n<p>Once the test theme was working I had to make one more theme, to prove that the design worked in general.\u00a0 So I created the roman theme, which is simply an old clock face with roman numerals.\u00a0 The hands were run through GIMP a few times to clean them up and remove colored areas around the hands that were an artifact of the poor original image.<\/p>\n<div id=\"attachment_5883\" style=\"width: 310px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.graphics-muse.org\/wp\/?attachment_id=5883\" rel=\"attachment wp-att-5883\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-5883\" class=\"wp-image-5883 size-medium\" src=\"https:\/\/i0.wp.com\/www.graphics-muse.org\/wp\/wp-content\/uploads\/2016\/12\/piclock-roman-300x169.png?resize=300%2C169\" width=\"300\" height=\"169\" \/><\/a><p id=\"caption-attachment-5883\" class=\"wp-caption-text\">The roman theme doesn&#8217;t use an overlay, but the overlay image is required because I didn&#8217;t go to the length of allowing it to be optional. The the image is simply the size of the clock face image but is completely transparent.<\/p><\/div>\n<p>The theme design worked perfectly on the second theme.\u00a0 I created it to spec and updated the default theme name, then ran PiClock.\u00a0 It displayed as shown.\u00a0 No code changes were required.\u00a0\u00a0 I&#8217;ve got some videos of the clocks running, if anyone is interested in watching a clock ticking.<\/p>\n<p><iframe loading=\"lazy\" width=\"1170\" height=\"878\" src=\"https:\/\/www.youtube.com\/embed\/N07iSc6vCSQ?feature=oembed\" frameborder=\"0\" allowfullscreen><\/iframe><\/p>\n<p><iframe loading=\"lazy\" width=\"1170\" height=\"878\" src=\"https:\/\/www.youtube.com\/embed\/qUyAUNspHcQ?feature=oembed\" frameborder=\"0\" allowfullscreen><\/iframe><\/p>\n<p>It&#8217;s not perfected quite yet, however.\u00a0 I need to add an option to cycle through themes.\u00a0 But its the holidays and I&#8217;ve already spent far too much time on PiBox so it&#8217;s time to let it go a bit and get back to the things that matter.\u00a0 At least for awhile.<\/p>\n<\/div><\/div>","protected":false},"excerpt":{"rendered":"<p>Themes with Cairo were always a goal, but never implemented.  Now, with PiClock, they are a reality.<\/p>\n","protected":false},"author":1,"featured_media":255,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":"","jetpack_publicize_message":"PiBox: PiClock themes using Cairo","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[2],"tags":[],"class_list":{"0":"post-260","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-tech","8":"czr-hentry"},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.piboxproject.com\/wp-content\/uploads\/2016\/12\/piclock-roman.png?fit=1366%2C768&ssl=1","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p8du2Y-4c","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/posts\/260","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/comments?post=260"}],"version-history":[{"count":1,"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/posts\/260\/revisions"}],"predecessor-version":[{"id":261,"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/posts\/260\/revisions\/261"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/media\/255"}],"wp:attachment":[{"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/media?parent=260"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/categories?post=260"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.piboxproject.com\/index.php\/wp-json\/wp\/v2\/tags?post=260"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}