Added ruby documentation for introduction to using Moving Images authored by Kevin Meaney's avatar Kevin Meaney
...@@ -4,11 +4,129 @@ ...@@ -4,11 +4,129 @@
For an introduction to what MovingImages is please see [Home](Home). For an introduction to what MovingImages is please see [Home](Home).
To create, modify, access and send messages to the MovingImages base object you use smig subcommands. You drive MovingImages using smig commands. From scripting languages like ruby, python etc. this consists of creating json objects. In AppleScript you create property lists. The property list or json object is passed to smig using the smig sub command "performcommand" smig converts that data into a dictionary representation which it the sends to MovingImages Launch Agent.
### Smig subcommands When interacting with MovingImages via smig commands at the command line, smig converts the command line options into a dictionary representation which it sends to the MovingImages Launch Agent.
The first argument on the command line after the smig command is a subcommand. There are six subcommands that smig recognizes. They are: Included with MovingImages is a library of ruby code that makes creating the JSON data to be sent to the MovingImages LaunchAgent simple.
### Ruby
Ruby is a scripting language that comes with OS X, as well as being able to write standalone scripts ruby has an interactive environment called irb which makes it easy to try things out. To access irb just type irb in terminal. If you are completely new to ruby I would recommend the [20 minute ruby introduction](https://www.ruby-lang.org/en/documentation/quickstart/).
MILA comes with a ruby script library for interacting and giving MILA instructions. The library is wrapped in a ruby module called MovingImages. When the ruby library is a little more stable I will make the library into a ruby gem.
[The MovingImages ruby reference documentation](http://www.yvs.eu.com/MovingImages/RubyReference/).
To have access to MovingImage's ruby library whilst using interactive ruby (irb) you will need to change directories in terminal to the directory where you have installed the ruby scripts before type irb. This will be easier when I've created a ruby gem. for now if you've installed the scripts into the default location using the MovingImages application then in terminal typing:
cd ~/scripts
irb
will start an interactive ruby session with access to the MovingImage ruby library.
Within the MovingImages module are a collection of modules and classes. The module Smig is the one used for sending commands to MILA using smig. A few small commands that are used often in irb are included in Smig for convenience.
A convenience method to close all base objects in MovingImages is:
```ruby
MovingImages::Smig.closeall_nothrow()
```
To reduce what you need to type the MovingImages:: prefix can be left out if earlier on in the script or in irb "include MovingImages" has been run. If you start irb from within the scripts folder as described above then "include MovingImages" will have already been done for you.
The Smig methods will raise an exception if the command line tool "smig" returns an error code unless the methods have nothrow in their method name. The closeall method is a bit dangerous because if a script is running that has nothing to do with who sent the closeall_nothrow message then any objects that script created will also be closed. I only use this method in interactive ruby when I've been testing things out and I know I have no other scripts running. I do not use it in complete scripts.
If you have a single command that you want performed you can just call:
```ruby
Smig.perform_command(theCommand)
```
where "theCommand" is the command to be run, see further down for how to create a command.
More commonly you will have a [SmigCommands](#smig-commands) :TODO (confirm link) object which contains a list of commands to be performed, a SmigCommands object has a few properties for customizing how the commands should be run or how the results of the commands should be returned.
```ruby
Smig.perform_commands(theCommands)
```
The CommandModule module of MovingImages wraps all the methods and classes that relate to making and modifying commands and the SmigCommands class.
Any CommandModule method that begins with "make_", makes a command object.
```ruby
addImageCommand = CommandModule.make_addimage(receiverObject, imageSource, imageIndex: 0)
```
The above makes a command that will be sent to the receiver object which in this example will need to be an image exporter object as it is the only object type that understands the "addimage" command. The imageSource is the object that provides the image to be added to image exporter and imageIndex is optional. If the image to be added is from a image importer object and the imported image file contains more than one image and you don't want the first image you will need to specify imageIndex with the desired image index. A bitmap context object and a nsgraphics context (window) are also valid objects to be an image source.
After a command has been created, it can either be run immediately using:
```ruby
Smig.perform_command(addImageCommand)
```
or it can be added to a SmigCommands object.
A SmigCommands object is created:
```ruby
theCommands = SmigCommands.new
```
You can set various properties of a SmigCommands object, the following demonstrates some of these:
```ruby
theCommands.set_run_asynchronously(true) # default is false
theCommands.set_stoponfailure(true) # default is true
theCommands.set_informationreturned(:lastcommandresult)
theCommands.set_saveresultstype(:jsonfile)
theCommands.set_saveresultsto("~/movingimagesresults.json")
```
The above sets up the SmigCommands object so that the commands will be run asynchronously (not concurrently, the commands will be run one after the other, but that the commands will be put into a queue to be run and then MovingImages will return and become available to be sent another list of commands). If there is a failure running any command then no more commands in that list will be run (except cleanup commands). The result of the last command to be run will be saved to a json file called "movingimagesresults.json". This way you can check when the commands were completed, you can also check the contents to see if an error occurred.
To add a command to be performed to a SmigCommands object you do:
```ruby
theCommands.add_command(addImageCommand)
```
There is also a add_tocleanupcommands_closeobject method. This method takes an object id which is a way for MovingImages to identify an object internally and creates a close object command and then adds that command to the list of clean up commands. The cleanup commands are a list of commands that get run when the last command to run in the main command list is finished. This provides a mechanism for ensuring all objects that are created that need to be closed will be closed whether the commands in the main list were run without failure or not.
SmigCommands objects also has some convenience methods for making commands that create objects. I added these methods because I noticed I was repeatedly following the same pattern for making create object commands.
So if I want to make a create bitmap context command:
```ruby
bitmapSize = MIShapes.make_size(900, 600)
bitMapObjectID = theCommands.make_createbitmapcontext(size: bitmapSize,
addtocleanup: true,
preset: "AlphaPreMulFirstRGB8bpcInt")
```
which will make the create bitmap context command, add it to the SmigCommands object list of commands , add a close object command to the cleanup command list, and returns an object id which you can use to refer to the object when sending commands to the bitmap context object. When creating those commands the bitmapObjectID is the receiver object.
The MovingImages Framework identifies a base object from the information in a simple dictionary/json object. The SmigIDHash module provides a number of methods for creating the dictionary/json object representation of an internal MovingImages base object. When object creation and closing happens within the one list of commands the best way to refer to an object is to provide a unique name when you make the create object command. This is what a SmigCommands object does when you ask it to create an object. The uuid method of the SecureRandom module makes it easy to create unique names and this is what I use. To limit the scope within which to find an object with a particular name the object type is also specified. In this situation the SmidIDHash method is called like so:
```ruby
bitmapName = SecureRandom.uuid
bitmapObjectID = SmigIDHash.make_objectid(objecttype: :bitmapcontext, objectname: bitmapName)
```
When an object is created MovingImages gives it a unique object reference, if the command that created the object was the last to run then the result returned will be the object reference. You can create an object id using an object reference:
```ruby
theCommand = CommandModule.make_createbitmapcontext(size: bitmapSize)
theResult = Smig.perform_command(theCommand)
bitmapObjectID = SmigIDHash.make_objectid(objectreference: theResult.to_i)
```
This is the basics needed to be understood for scripting MovingImages using ruby.
### Command line smig
The first argument on the command line after the smig command is a subcommand. There are seven subcommands that smig recognizes. They are:
+ create + create
+ getproperty + getproperty
...@@ -16,6 +134,7 @@ The first argument on the command line after the smig command is a subcommand. T ...@@ -16,6 +134,7 @@ The first argument on the command line after the smig command is a subcommand. T
+ getproperties + getproperties
+ setproperties + setproperties
+ doaction + doaction
+ performcommand
#### create #### create
...@@ -101,6 +220,19 @@ Example of setting the properties metadata to an image in a image exporter from ...@@ -101,6 +220,19 @@ Example of setting the properties metadata to an image in a image exporter from
smig setproperties -object 2 -imageindex 0 -jsonfile "/Users/superscripter/Documents/propertyfile.json" smig setproperties -object 2 -imageindex 0 -jsonfile "/Users/superscripter/Documents/propertyfile.json"
#### performcommand
The performcommand subcommand takes one of three options "-jsonstring", "-jsonfile", "-propertyfile". If the option is "-jsonstring" then the next argument is a json string object which smig will send to the MovingImages Launch Agent (MILA), if the option is "-jsonfile" then the next argument is a path to a json file containing the data to be sent to MILA, if the option is "-propertyfile" then the next argument is a path to a property list file containing the data to be sent to MILA. The following will return the number of MILA base objects:
smig performcommand -jsonstring '{"commands":[{"command":"getproperty","propertykey":"numberofobjects"}]}'
This subcommand provides a single entry point for scripting languages to access MILA. For example in ruby the following will produce the same result:
```ruby
Smig.get_numberofobjects()
```
Which basically creates the json text data and then sends the same perform command message as the command line version above.
#### doaction #### doaction
...@@ -118,59 +250,56 @@ Example of the close doaction subcommand that closes the referenced object: ...@@ -118,59 +250,56 @@ Example of the close doaction subcommand that closes the referenced object:
smig doaction -close -object 0 smig doaction -close -object 0
### Base objects #### The "-drawelement" "doaction"
There are four different types of base objects that have been implemented. These are "imageimporter", "bitmapcontext", "imageexporter", and "imagefilterchain". More will be implemented in future updates. You are responsible for creating and disposing these objects in your scripts. The draw element do action is a smig subcommand that has it's own vocabulary which is quite extensive, describing what to draw, how to draw and where to draw.
The usual way to refer to a base object is by its reference which is the value returned by the create subcommand. The reference is unique for the lifetime of the object, and as long as your not leaking base objects, it will be a long time before that object reference will be reused. There are two alternative ways to refer to base objects, both methods use the base object type and one method uses a name which you can give a base object when you create it, or you can use an index into the list of objects of that type. You can use the getproperty subcommand to find out the number of objects of a particular type. Using object type and index will not always return the same object because an object's index can change when other objects of the same type are deleted. The object reference does not change during the lifetime of an object. The draw element do action is an action that a graphic context object handles. The information for performing the draw element action can be sourced from one of three places. If the command line includes a -jsonstring option, then the text in quotes that follows is a json string and contains the information needed for performing the draw element do action. If not, then the information needed to perform the drawelement action is sourced from a property file (extension .plist) or a json file (extension .json).
The image importer base object is used to import image files and is a source for images to the bitmap context and image exporter. The bitmap context base object manages a bitmap into which you draw shapes and images. The bitmap context is also a source for images to the image exporter. The image exporter takes images from image importers and bitmap contexts and writes those images to disk. Drawing into the bitmap context is an important part of the MILA. Simple shapes like rectangles and ovals can be drawn either filled or with only their outline. The same goes for complex shapes created from paths. The line and fill color can be specified, the line width and how lines join can be specified. Text can be drawn using various fonts and other text drawing attributes. Images from a image importer or another graphic context can be drawn. All drawing can be done using various blend modes and by using a number of geometical transformations. All this information is represented in a textual form, one of a json string, a json file or a plist file, and is represented by key value pairs. Every json string must contain a "elementtype" key. The "elemenetttype" key defines the type of element to be drawn, like for example a rectangle.
Objects with the object type "imageimporter", "imageexporter", and "bitmapcontext" are fairly simple to create only requiring a few options. The "imagefilterchain" object though is more complex to create as it needs all the necessary information for creating the CoreImage filter chain that the "imagefilterchain" object manages. smig doaction -drawelement -object $BMCR -plistfile "/path/to/drawshapes.plist"
### JSON objects and plist Dictionaries #### The "-renderfilterchain" "doaction"
Moving Images uses two different formats to represent the information needed for the drawing commands, for creating the image filter chain object, and for describing values that change each time a filter chain is rendered. The way Moving Images information is structured within a JSON object and a plist dictionary is exactly the same, but the terminology used to describe JSON objects and plist dictionaries is different. When reading the documentation for Moving Images it is sensible to keep this in mind. The render filter chain do action is a smig subcommand that can be sent to a base object with type "imagefilterchain". In its simple form the render action can be sent to a image filter chain object to render the filter chain in its current state.
A JSON object is equivalent to a plist dictionary. A member or key/value pair of a dictionary is the same as a member or property of a JSON object. The JSON equivalent of the key part of a dictionary key/value pair is keyword. Keys/keywords can only be strings, whereas their values can be text, a number, a bool value, a dictionary/JSON object, or an array. smig doaction -renderfilterchain -object $IFCR
A JSON array is equivalent to a plist array and in both cases you refer to elements of the array as items or elements. But the render filter chain action can take any one of three command line options: "-jsonstring", "-jsonfile", or "-plistfile". The "-jsonstring" option means that you provide a string on the command line after the option which defines how the filter chain is updated before rendering. You can also specify a source and destination rectangle for rendering the filter chain within the string all using the JSON format. The "-plistfile" and "-jsonfile" options both need to be followed by a path to a file which has the same purpose as the "-jsonstring" option. The file options is more likely to be used when the information needed to update the filter chain before rendering is large.
### The "-drawelement" "doaction" smig doaction -renderfilterchain -object $IFCR -jsonfile "/Path/To/RenderFilter.json"
The draw element do action is a smig subcommand that has it's own vocabulary which is quite extensive, describing what to draw, how to draw and where to draw. ### Base objects
The draw element do action is an action that a graphic context object handles. The information for performing the draw element action can be sourced from one of three places. If the command line includes a -jsonstring option, then the text in quotes that follows is a json string and contains the information needed for performing the draw element do action. If not, then the information needed to perform the drawelement action is sourced from a property file (extension .plist) or a json file (extension .json). There are six different types of base objects that have been implemented. These are "imageimporter", "bitmapcontext", "imageexporter", "nsgraphicscontext", "pdfcontext", and "imagefilterchain". More will be implemented in future updates. You are responsible for creating and disposing these objects in your scripts.
Drawing into the bitmap context is an important part of the MILA. Simple shapes like rectangles and ovals can be drawn either filled or with only their outline. The same goes for complex shapes created from paths. The line and fill color can be specified, the line width and how lines join can be specified. Text can be drawn using various fonts and other text drawing attributes. Images from a image importer or another graphic context can be drawn. All drawing can be done using various blend modes and by using a number of geometical transformations. All this information is represented in a textual form, one of a json string, a json file or a plist file, and is represented by key value pairs. Every json string must contain a "elementtype" key. The "elemenetttype" key defines the type of element to be drawn, like for example a rectangle. The usual way to refer to a base object is by its reference which is the value returned by the create subcommand. The reference is unique for the lifetime of the object, and as long as your not leaking base objects, it will be a long time before that object reference will be reused. There are two alternative ways to refer to base objects, both methods use the base object type and one method uses a name which you can give a base object when you create it, or you can use an index into the list of objects of that type. You can use the getproperty subcommand to find out the number of objects of a particular type. Using object type and index will not always return the same object because an object's index can change when other objects of the same type are deleted. The object reference does not change during the lifetime of an object.
smig doaction -drawelement -object $BMCR -plistfile "/path/to/drawshapes.plist" The image importer base object is used to import image files and is a source for images to the bitmap context and image exporter. The bitmap context base object manages a bitmap into which you draw shapes and images. The bitmap context is also a source for images to the image exporter. The image exporter takes images from image importers and bitmap contexts and writes those images to disk.
### The "-renderfilterchain" "doaction" Objects with the object type "imageimporter", "imageexporter", and "bitmapcontext" are fairly simple to create only requiring a few options. The "imagefilterchain" object though is more complex to create as it needs all the necessary information for creating the CoreImage filter chain that the "imagefilterchain" object manages.
The render filter chain do action is a smig subcommand that can be sent to a base object with type "imagefilterchain". In its simple form the render action can be sent to a image filter chain object to render the filter chain in its current state. ### JSON objects and plist Dictionaries
smig doaction -renderfilterchain -object $IFCR Moving Images uses two different formats to represent the information needed for the drawing commands, for creating the image filter chain object, and for describing values that change each time a filter chain is rendered. The way Moving Images information is structured within a JSON object and a plist dictionary is exactly the same, but the terminology used to describe JSON objects and plist dictionaries is different. When reading the documentation for Moving Images it is sensible to keep this in mind.
But the render filter chain action can take any one of three command line options: "-jsonstring", "-jsonfile", or "-plistfile". The "-jsonstring" option means that you provide a string on the command line after the option which defines how the filter chain is updated before rendering. You can also specify a source and destination rectangle for rendering the filter chain within the string all using the JSON format. The "-plistfile" and "-jsonfile" options both need to be followed by a path to a file which has the same purpose as the "-jsonstring" option. The file options is more likely to be used when the information needed to update the filter chain before rendering is large. A JSON object is equivalent to a plist dictionary. A member or key/value pair of a dictionary is the same as a member or property of a JSON object. The JSON equivalent of the key part of a dictionary key/value pair is keyword. Keys/keywords can only be strings, whereas their values can be text, a number, a bool value, a dictionary/JSON object, or an array.
smig doaction -renderfilterchain -object $IFCR -jsonfile "/Path/To/RenderFilter.json" A JSON array is equivalent to a plist array and in both cases you refer to elements of the array as items or elements.
### Error handling ### Command line error handling
Every time you run any smig subcommand smig returns a value. A return value of 0 indicates success and the resulting string is valid output. Any other return value indicates that an error occurred and the value is an error code. When this happens the text returned by smig contains a short error message. A missing command option, an invalid command option value, or that an error occurred performing an action, are typical error values. When an error occurs the return string will be a short error message starting with "Error:". Every time you run any smig subcommand smig returns a value. A return value of 0 indicates success and the resulting string is valid output. Any other return value indicates that an error occurred and the value is an error code. When this happens the text returned by smig contains a short error message. A missing command option, an invalid command option value, or that an error occurred performing an action, are typical error values. When an error occurs the return string will be a short error message starting with "Error:".
To output the value returned from running smig, you add echo $? on the line following the smig command. You can use this value to test if an error occurred and decide how to progress in your script. It is at least necessary to check whether an error occurred when you are using the result in the script. For example, when creating a base object, if an error occurs the result string won't contain the object reference but an error string. The getproperty subcommand returns a string representing the value requested, whilst the getproperties subcommand can return a long json string representing an object structure that captures all the properties returned by the subcommand. The -getstringdrawingsize action returns a string with information about the width and height needed to draw a string. All these operations will have an error string in place of the string with the information you requested, so at least in these cases, you should check the error code before proceeding. When running bash scripts or using smig at the command line to output the value returned from running smig, you add echo $? on the line following the smig command. You can use this value to test if an error occurred and decide how to progress in your script. It is at least necessary to check whether an error occurred when you are using the result in the script. For example, when creating a base object, if an error occurs the result string won't contain the object reference but an error string. The getproperty subcommand returns a string representing the value requested, whilst the getproperties subcommand can return a long json string representing an object structure that captures all the properties returned by the subcommand. The -getstringdrawingsize action returns a string with information about the width and height needed to draw a string. All these operations will have an error string in place of the string with the information you requested, so at least in these cases, you should check the error code before proceeding.
### Debugging ### Debugging
Particularly during script development, it is necessary to be able to debug your smig commands. Checking error codes as described above is essential, but there are other things you can do. Particularly during script development, it is necessary to be able to debug your smig commands. Checking error codes and messages as described above is essential, but when something goes wrong the place to look is in the log file.
MovingImages also has a log file. The log file is in the ~/Library/logs directory and is called "U6TV63RN87.com.yvs.MovingImagesAgent YYYY-MM-DD HH-mm.log" where the "YYYY-MM-DD HH-mm" part of the file name is the date and time when the log file was first created. The easiest way to view the log file is to use the Console application. Output is sent to the log file when something goes wrong whilst creating filter chains, rendering filter chains or drawing shapes and images. There is usually multiple messages sent for each problem, the first message outputs the object where something is missing and then each following message provides greater context within which the problem occurred until finally you get what I call railroad tracks. A line of equal signs like so "======================================" which means the end of messages relating to this particular problem.
Currently the remaining debugging support relates to the bitmap context and draw element commands. You can send a "-showwindow YES" "doaction" message to a bitmap context base object. The displayed window is a palette window that cannot be moved or resized. The only way to get rid of the window is to send a "-showwindow NO" "doaction" message. On a single display computer the window will cover a maximum of the bottom right quarter of the display, and on a computer with multiple displays the window will be displayed on the screen which currently does not have a window which has focus. While the window is displayed all drawing commands sent to the bitmap context will also be displayed in the shown window. MovingImages has a log file which is easy to view using the console application. The console automatically keeps track of log files saved in standard locations, all you need to do is locate the log file within the console application. The log file is in the ~/Library/logs directory and is called "U6TV63RN87.com.yvs.MovingImagesAgent YYYY-MM-DD HH-mm.log" where the "YYYY-MM-DD HH-mm" part of the file name is the date and time when the log file was first created. Output is sent to the log file when something goes wrong when performing any MovingImages action. There is usually multiple messages sent for each problem, the first message is where a problem was first detected and the following messages provides greater context within which the problem occurred as the command is unwound until finally you get what I call railroad tracks. A line of equal signs like so "======================================" which means the end of messages relating to this particular problem.
The other debugging support relates to the recording of drawing element actions. You start the recording by sending a "-starthistory" "doaction" message to the bitmap context object. As well as starting the recording, this action takes a snapshot image of the context in its current state. You can then try out various drawing commands ,one at a time on the command line, and if you didn't like the result of the last drawing command you can send the "-historydroplast" "doaction" message to the bitmap context object and your last drawing element action will be dropped from the history. You can replay the history by sending the "-historyreplay" "doaction" message to the bitmap context object, and the snapshot is drawn, then followed by a replay of the drawing history. Once you have got your drawing elements working as you would like, you can send a "-historysave" "doaction" message to the bitmap graphic context. This will save your drawing history to a plist or a json file. This file can be used as an input into a single draw element do action message.
### Memory management ### Memory management
... ...
......