Display images at random positions

From SpinetiX Support Wiki

Jump to: navigation, search

This page is related to jSignage.

Introduction

Final result

This page contains a tutorial showing how to create a custom widget to display multiple images on the screen. The images are located in a specific folder and displayed at random position in the screen.

The tutorial Create custom image animation improves further the result of this tutorial by adding a fly down and a zooming effect on the images.

Tutorial

  • Difficulty: Medium, programing skills required.
  • Total duration: 20-30 minutes.
  • Requirements:

Getting started

This first section explain how to create the structure and the files needed to build the new widget.

  • Create an Elementi Project to hold and test the widget as explained in Project creation. We will call it ImageAnim. Make sure you select HMP200 as the target model and an indefinite duration.
  • Create a new Collection and call it media.
    • Open the collection
    • Drag and drop from an explorer window all the images that you want to be displayed by the widget as explained in file import.
  • Open an explorer window in the selected project.
    • Use the right click menu and select "Open folder in Explorer" (Elementi X only).
    • If you haven't modified the default folder for the Projects in Elementi, you can run the following command from the start menu: shell:Personal\SpinetiX\My Projects\ImageAnim. You should see a file call index.svg.
  • Create the widget SVG document that will be displayed by the HMP and Elementi. In this example we will create it from scratch.
    • In the explorer window, create two new file, and call them anim.svg and anim.js
    • Open anim.svg in your editor. If you are using Notepad++, you can select the file and using the right click menu, select "Edit with Notepad++".
    • An empty file should be opened in your editor.
  • Create the skeleton for the SVG code
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" dur="indefinite" viewBox="0 0 1920 1080" >
  <script xlink:href="http://download.spinetix.com/spxjslibs/jSignage.js"/>
  <script xlink:href="anim.js"/>
  <g id="layers"/>
</svg>

Let explain the different line of code:

  • <svg [...] dur="indefinite" viewBox="0 0 1920 1080" >
    This describes the SVG document. It enforce an indefinite duration (i.e. the document will never ends) and a size that fill up the entire full HD screen.
  • <script xlink:href="http://download.spinetix.com/spxjslibs/jSignage.js"/>
    This insure that the jSignage library is loaded and can be used in the rest of the development as explained in Loading the library.
  • <script xlink:href="anim.js"/>
    This will include the script that we are going to develop in this tutorial.
  • <g id="layers"/>
    This is a generic place older, where we are going to add the images to be displayed.

First script

Now that the file structure is in place, we can start creating the display script.

  • Open anim.js in your editor.

Create the configuration object

We will first add a configuration object to our script, where we store all the options of our widget.

var config = {
    "dataSource": { "type": "uri", "src": "media", "parser": { "resourcetype": "file", "type": "dir" } },
    "nbImage": 20,
    "width" : 384, "height" : 216
};

Let's explain the various part of the config object:

  • dataSource
    This describes where the data (the images) are located. The describes the parameter needed by the $.getData() function of jSignage. The current config uses the following:
    • "type": "uri"
      The datasource is an uri, i.e. the content of a folder
    • "src": "media"
      The folder where the 'data' (the files) are located is called 'media'
    • "parser": { "resourcetype": "file", "type": "dir" }
      This indicates how the content of the folder should be interpreted. The type should always be set to "dir" when using an uri datasource. The "resourcetype" is set to "file" are we are only interested in the files located in the "media" folder and not possible sub folders.
  • nbImage:
    This coresponds to the number of images we want to display on the screen simultaneously.
  • width, height:
    Width and height of the image displayer on the screen. Note that for best performance, this should match the size of the imported images.

Fetching the images

The first action is to fetch the list of images to be displayed on the screen.

$(function(){
    $.getData( config.dataSource, data_display );
});

Let's explain the various line of the code:

  • $(function(){ ... }
    The is the onLoad callback of jSignage, It insure that the code inside the { ... } will only be executed once all the document has been loaded. You should always put your code in this callback to avoid being dependent on the order of the xml element in the SVG file.
  • $.getData( config.dataSource, data_display );
    This will fetch the data according to the dataSource config. Once the data has been retrieved and parsed, the callback function data_display() will be called. This function is the one responsible for displaying the data and will be explained in the next section.

Selecting the images

The next step is to select the images to be displayed. We will pick up a random set of images from all the file on the folder and displaying them.

function data_display( data ) {
    for ( var i=0; i<config.nbImage; i++ ) {
        var imgIdx = Math.floor( Math.random() * data.length );
        var href = data[imgIdx].href;
        display_image( href );
    }
}

Let's explain the various line of the code:

  • function data_display( data ) { ... }
    This is the display function that is called by $.getData(). It receives the list of image in a data array. Each entry of the data array is an object with a set of attribute describing each media, such as the filename, the href (location of the media), the creation date,...
  • for ( var i=0; i<config.nbImage; i++ ) { ... }
    Creates a loop to display the "nbImage" on the screen.
  • var imgIdx = Math.floor( Math.random() * data.length );
    Use a random generator to select one of the image from the data.
  • var href = data[imgIdx].href;
    Retrieve the uri of the image to be displayed.
  • display_image( href );
    Call the display function itself that will be explained in the next section.

Displaying the images

The final part is to display the images on the screen.

function display_image( href ) {
    var x = Math.round( Math.random() * (1920-config.width) );
    var y = Math.round( Math.random() * (1080-config.height) );
    var media = $.image( {
        href: href,
        top: y, left: x,
        height: config.height, width: config.width
    });
    media.addTo('#layers');
}

Let's explain the various line of the code:

  • function display_image( href ) { ... }
    This is the image display function that will display the image identified by the href.
  • var x = Math.round( Math.random() * (1920-config.width) );
    Generate and random value for the x coordinate of the image. As we don't want images to be outside the screen, we need to subtract the width of the image from the total width at disposal.
  • var y = Math.round( Math.random() * (1080-config.height) );
    Same as the previous line, but for the y coordinate.
  • var media = $.image( { ... })
    Create an image element using the $.image() function of jSignage.
  • media.addTo('#layers');
    Add the image to the "layer" placeholder in the SVG document.

Putting it all together

Finally, we can create the full script putting all component together:

var config = {
    "dataSource": { "parser": { "resourcetype": "file", "type": "dir" }, "src": "media", "type": "uri" },
    "nbImage": 20,
    "width" : 384, "height" : 216
};
function display_image( href ) {
    var x = Math.round( Math.random() * (1920-config.width) );
    var y = Math.round( Math.random() * (1080-config.height) );
    var media = $.image( {
        href: href,
        top: y, left: x,
        height: config.height, width: config.width
    });
    media.addTo('#layers');
}
function data_display( data ) {
    for ( var i=0; i<config.nbImage; i++ ) {
        var imgIdx = Math.floor( Math.random() * data.length );
        var href = data[imgIdx].href;
        display_image( href );
    }
}
$(function(){
    $.getData( config.dataSource, data_display );
});

Note that the data_display( ) must be declared before being used.

Opening the anim.svg in Elementi, will display 20 random images in the screen at random positions.

Improving the script

One of the major drawback of the above script, is that all the images are displaying on the same time, and that once they are displayed nothing changes in the screen.

The fact that all images are displaying on the same time, will create performance issue on the player, limiting the number of images that can be displayed. It would be much more interesting if the images where appearing one by one, staying on the screen for a limited time, and then disappearing.

New configuration

The first step is to add a new configurable duration for all images. This is done first by adding a "duration parameters to the configuration object:

var config = {
    "dataSource" : { "parser" : { "resourcetype" : "file", "type" : "dir" }, "src" : "media", "type" : "uri" },
    "nbImage" : 20,
    "duration" : 10,
    "width" : 384, "height" : 216
};

Adding images

The next step is to add the images one by one at regular interval. For this we will modify the data_display( ) function and replace the for loop by a timer.

function data_display( data ) {
    var delay = config.duration / config.nbImage;
    $.setInterval( function() {
        var imgIdx = Math.floor( Math.random() * data.length );
        var href = data[imgIdx].href;
        display_image( href );
    }, delay *1000 );    
}

Let's explain the various line of the code:

  • var delay = config.duration / config.nbImage;
    We need to compute the delay at which image will be added to the screen. As we want to have "nbImage" visible for the given "duration", we need to add one new image every "config.duration / config.nbImage";
  • $.setInterval( function() { ... }, delay*1000 );
    Now that we have the delay at which a new image needs to be added, we can create a timer (using $.setInterval() ) to add the next image. The first parameters is an anonymous function executed each time the delay is passed.

Note that this function never stops, and will add a new images every delay seconds.

Image duration

Finally a duration need to be applied to each new media.

function display_image( href ) {
    var x = Math.round( Math.random() * (1920-config.width) );
    var y = Math.round( Math.random() * (1080-config.height) );
    var media = $.image( {
        href: href,
        top: y, left: x,
        height: config.height, width: config.width,
        dur: config.duration
    });
    media.removeAfter();
    media.addTo('#layers');
}

Note that an additional line has been added:

  • media.removeAfter();
    Specify that the media should be removed from the DOM (the rendering tree) once it is no longer displayed. This is necessary to insure that we do not fill up the document with media (even if they are no longer visible) and that the player crashes after some long rendering time.

Putting all the modification together, in the original script is left as an exercise to the reader.

Improving the user experience

Using the above script exactly produce the desired output on the screen. The major drawback is that the user need to edit the script to modify any of the parameters. We can use the Elementi widget authoring features to make all the parameters configurable from Elementi directly.

Modifying the interface

The interface displayed by Elementi is controlled by the anim.svg file. So wee need to open this file in the editor and modify it to the following:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" dur="indefinite" viewBox="0 0 1920 1080">
 <defs xml:id="jsonConfig"><![CDATA[{
    "dataSource" : { "parser" : { "resourcetype" : "file", "type" : "dir" }, "src" : "media", "type" : "uri" },
    "nbImage" : 20,
    "duration" : 10,
    "width" : 384, "height" : 216
}]]></defs >
  <spx:properties xmlns:spx="http://www.spinetix.com/namespace/1.0/spx">
    <spx:json-data name="Data source" propertyName="dataSource" type="uri" xlink:href="#jsonConfig"/>
    <spx:json-number name="Nb images" propertyName="nbImage" xlink:href="#jsonConfig"/>
    <spx:json-number name="Image duration" propertyName="duration" xlink:href="#jsonConfig"/>
    <spx:json-number name="Image width" propertyName="width" xlink:href="#jsonConfig"/>
    <spx:json-number name="Image height" propertyName="height" xlink:href="#jsonConfig"/>
  </spx:properties>
 <script xlink:href="http://download.spinetix.com/spxjslibs/jSignage.js"/>
 <script xlink:href="anim.js"/>
 <g id="layers"/>
</svg>

We can see two major additions: the <defs> and the <spx:properties>.

The <defs> contains all the configurable parameters that where previously in the script. The configuration is now expressed using a JSON string. The content of the configuration itself has not been modified.

The <spx:properties> controls the user interface itself using customization_tags.

  • <spx:json-data name="Data source" propertyName="dataSource" type="uri" xlink:href="#jsonConfig"/>
    Add the interface to configure the datafeed (i.e. the location of the images), and let the user add customer selection rules if needed. This allows to get the full power of Elementi for feeds selection and filtering in the new widget.
    • xlink:href="#jsonConfig"
      Indicates where the configuration data is located (in the SVG file in the element with id euqald to 'jsonConfig'.
    • propertyName="dataSource"
      Name of the properties modified by this element of the interface.
  • <spx:json-number name="Nb images" propertyName="nbImage" xlink:href="#jsonConfig"/>
    Let the user configure the number of images to be displayed. The interface will validate that the data entered by the user is indeed a number.

Modifying the script

As we now rely on Elementi to configure the parameters, we need to modify the script to use the new configuration:

var config = $.parseJSON( $('#jsonConfig').text() );
  • $('#jsonConfig').text()
    Retrieve the configuration from the SVG file.
  • $.parseJSON( ... );
    Parse the configuration from a JSON string to a JavaScript object that can be used by the rest of the script.

Putting it all together

The final version of the widget can be downloaded using the link on the left. A set of smiley are use as images, but you can edit the content of the media folder using Elementi.

This page was last modified on 14 November 2017, at 13:44.