Script Manual |
When Shake saves a script, it is saving it in a language that is basically the C programming language. This makes the product open and flexible, as users can add in their own functions, or use programing structures to procedurally do what would otherwise be extremely tedious to do by hand. By editing the text scripts, the user can also quickly make minor modifications to it that would otherwise be cumbersome to do in the interface, for example changing a FileIn to read BG2.iff instead of BG1.rla. This section of the manual will discuss the basic principles of the scripting language, and how they can be manipulated to make your own interesting macros.
There is a mini-tutorial of sorts under Modifying Parameter Behavior in that it goes step-by-step through some ui scripting examples.
If you are a programmer, this document will insult you. Read the Tip box below on if you should precompile or use macros, maybe read the other Tip boxes too, just to be nice, and then contact us for the SDK. If you are accustomed to MEL, TCL or shell scripting, this document will also be a fast read.
There is also a tutorial on how to make a macro both in a script and interactively under GUI Advanced - How to Make a Macro.
Evaluate the DisplaceX, and enter into your xExpr
and yExpr the following expressions: x-r*50 Load the RGrad parameters and move the RGrad around in the Viewer to get a feeling for the speed. It should be somewhat poky, as the DisplaceX does not pre-compile the expression. Now, look at the IDisplace node, and enter an xScale of 50. This is a pre-compiled version of the DisplaceX we just made. Load up the RGrad parameters and move it around in the Viewer again. In comparison to the DisplaceX, it will be pretty snappy, as the displacement equation has already been precompiled. The conclusion? If you have access to a programmer, and you are going to use an effect rather frequently, precompile using the SDK. |
Again, this document is not about the SDK, so I will only be discussing macro creation and solutions accessible to the common user.
Scripting Principles |
Scripting Controls:
Let's start off the discussion by creating a script. Go to the doc/pix/truck directory and enter the following command:
shake truck.iff -outside sign_mask.iff -over bg.iff -savescript start.shk
This will composite the truck over the background, with the sign mask as a holdout mask, saving it as a script named start.shk. It would look like this in the interface:
To test the script, type
shake -script start.shk
It should pop the image of the truck comped over the street.
Here is what the script itself looks like, more or less:
SetTimeRange("1"); SetFieldRendering(0); SetFps(24); SetMotionBlur(1, 1, 0); SetQuality(1); SetProxyScale(1, 1); SetProxyFilter("default"); SetPixelScale(1, 1); SetDefaultWidth(720); SetDefaultHeight(486); SetDefaultAspect(1); // Input nodes bg = FileIn("bg.iff", "Auto", 0, 0); sign_mask = FileIn("sign_mask.iff", "Auto", 0, 0); truck = FileIn("truck.iff", "Auto", 0, 0); // Processing nodes Outside1 = Outside(truck, sign_mask, 1); Over1 = Over(Outside1, bg, 1, 0, 0);
The first section has controls for the script, all of which are optional or
can be overridden on the command-line. See below under Frequently
Used Script Commands, Routines, and Variables for more information. These
next sections will discuss the body of the script, the parts listed under the
Input and Processing nodes.
Variables and Data Types:
Shake assigns types of data to a variable name. A variable works as a sort of shopping cart (pick any metaphor here...) to carry around your information to be reused somewhere eles. The next bit from the above example can be taken all in one chunk. I've omitted the comment lines:
bg = FileIn("bg.iff", "Auto", 0, 0); sign_mask = FileIn("sign_mask.iff", "Auto", 0, 0); truck = FileIn("truck.iff", "Auto", 0, 0); Outside1 = Outside(truck, sign_mask, 1); Over1 = Over(Outside1, bg, 1, 0, 0);
I've highlighted the variables. This assigns three variables (bg, sign_mask, truck) to three FileIn nodes. These are then fed into an Outside node and an Over node, both of which are also assigned variable names.
|
Here is a good reason to use a script - I can quickly set the path for the images by using copy/paste functions in my editor. In this case, I am setting them to my absolute path. This path will of course be different on your machine, so don't necessarily enter /Shake2/doc/...that would be silly. Once this is done, Shake won't prompt me to find the absolute path each time I paste it in with Ctrl+V:
bg = FileIn("/Shake2/doc/pix/truck/bg.iff", "Auto", 0, 0); sign_mask = FileIn("/Shake2/doc/pix/truck/sign_mask.iff", "Auto", 0, 0); truck = FileIn("/Shake2/doc/pix/truck/truck.iff", "Auto", 0, 0); Outside1 = Outside(truck, sign_mask, 1); Over1 = Over(Outside1, bg, 1, 0, 0);
The left side is the variable name. You could change the variable name, but you would have to change them wherever they appear:
Gilligan = FileIn("/Shake2/doc/pix/truck/bg.iff", "Auto", 0, 0); Skipper = FileIn("/Shake2/doc/pix/truck/sign_mask.iff", "Auto", 0, 0); Lovey = FileIn("/Shake2/doc/pix/truck/truck.iff", "Auto", 0, 0); Thurston = Outside(Lovey, Skipper, 1); Ginger = Over(Thurston, Gilligan, 1, 0, 0);
Therefore, the left side of these lines is the variable to which you are assigning a value. The right side is the value, usually coming from a function like FileIn, Outside or Over. The function is called by its name, then it has its arguments between parentheses. The arguments, called parameters, are very specifically organized and always specify the same thing, i.e., the third parameter of Outside is 1, which is a numeric code to decide if you are taking the foreground or background resolution. How do I know this? I looked it up in the docs under Function By Name - O - Outside
The line ends with a semicolon. If we fed this modified script back into the Shake interface, it would look like this:
To better understand the role of variables, I will insert a color correction node, Mult, into script to color-correct the truck. Looking at the documentation (Function By Name - M - Mult), I see that its format looks like this:
image Mult(
image In, float red, float green, float blue, float alpha, float depth );
Therefore, I can do something like this to make the truck yellow:
Gilligan = FileIn("/Shake2/doc/pix/truck/bg.iff", "Auto", 0, 0); Skipper = FileIn("/Shake2/doc/pix/truck/sign_mask.iff", "Auto", 0, 0); Lovey = FileIn("/Shake2/doc/pix/truck/truck.iff", "Auto", 0, 0); MaryAnn = Mult(Lovey, 1,1,.2); Thurston = Outside(MaryAnn, Skipper, 1); Ginger = Over(Thurston, Gilligan, 1, 0, 0);
In the interface, it would look something like this:
Looking at the Mult documentation, you can see we omitted the alpha and depth parameters. These therefore defaulted to a value of 1. You can also see that Mult is looking for two types of data: an image (labeled In) and floats (labeled red, green, blue, etc). A float is any number with a decimal place in it, i.e., 1.2, 10.00, .00001, 100,000.00. There are five types of data:
Data Type | Notes |
image | uh, an image... |
float | a number with a decimal place - .001, 1.0, 10,000.00 |
int, or integer | a number with no decimal place - 0, 1, 10,000 |
string | "this is a string" |
curve float or curve int | A special case of float or int that designates that the number has the possibility of being changed during script execution or when tuning it in the interface. More on this in a bit. |
Shake is usually smart enough to convert an integer into a float, so when we entered
MaryAnn = Mult(Lovey, 1,1,.2);
it didn't get too freaked out that you entered two integers (1, 1) for the red and green multipliers. However, this is an infrequent case concerning the conversion of floats and integers, as you couldn't put in a string or an image for those arguments, i.e,
MaryAnn = Mult(Lovey,"1", Gilligan, .2);
wouldn't work because you are drastically mixing your data types. The one exception to this is when you don't want to designate any image input, in which case you put in a 0. Shake understands this means it is empty.
This example has no image input for the background image, the second argument: MaryAnn = Over(Thurston, 0); |
We have so far only assigned values to variables of image types. In this next example, we create a float variable because we want to multiply the truck image by the same amount on the red, green, and blue channels. I will call this variable mulVal.
For these examples, you must save the script and use Load Script - the Copy/Paste won't work properly.
float mulVal = .6; Gilligan = FileIn("/Shake2/doc/pix/truck/bg.iff", "Auto", 0, 0); Skipper = FileIn("/Shake2/doc/pix/truck/sign_mask.iff", "Auto", 0, 0); Lovey = FileIn("/Shake2/doc/pix/truck/truck.iff", "Auto", 0, 0); MaryAnn = Mult(Lovey, mulVal, mulVal, mulVal); Thurston = Outside(MaryAnn, Skipper, 1); Ginger = Over(Thurston, Gilligan, 1, 0, 0);
Now Mult takes .6 for all of its parameter. To change all three of them in the script, you simply can modify mulVal on the first line of the script and re-execute the script. This is swell, and you are probably patting yourself on the back on how blazingly brilliant you are. However, here is a problem. The variable mulVal is only applied when you load the script - Shake doesn't keep it around for later use. If you load the script (with Load Script, not Copy/Paste) into the interface and look at the Mult parameters, they all say .6; not mulVal. There is no place in the interface that you can find the mulVal parameter and modify it and have the Mult take the argument. We therefore must declare mulVal to be a curve float, a special type of float that basically tells Shake to wait until frame calculation to resolve the variable:
curve float mulVal = .6; Gilligan = FileIn("/Shake2/doc/pix/truck/bg.iff", "Auto", 0, 0); Skipper = FileIn("/Shake2/doc/pix/truck/sign_mask.iff", "Auto", 0, 0); Lovey = FileIn("/Shake2/doc/pix/truck/truck.iff", "Auto", 0, 0); MaryAnn = Mult(Lovey, mulVal, mulVal, mulVal); Thurston = Outside(MaryAnn, Skipper, 1); Ginger = Over(Thurston, Gilligan, 1, 0, 0);
Now when you load it into Shake (again, you can't Copy/Paste - you must use Load Script), you will find a new variable under localParameters under the Global tab called mulVal. You can modify it in the interface, and the Mult will update properly.
In short, if you are loading a script to be modified in the interface, you probably want to put it as a curve type of data. If you are just going to execute the script on the command-line and it isn't going to change (i.e., it isn't animated), you can omit the curve data type.
Don't worry, there are examples later on.
Functions
Shake is a collection of functions. Some functions modify images, like Blur. Some return a number, like distance() (this calculates the distance between two points) or cosd(). Other functions build the interface, like nuiToolBoxItem, which loads a button into the interface and attaches a function to it. When we call a function, we assign a variable to it, like the following example which reads the node named Rotate1's angle parameter, and returns the cosine of it in degrees, assigning it to the variable named myCos:
curve float myCos = cosd(Rotate1.angle);
You can also use functions inside of other functions. In this example, we are using the cosd function inside of a Rotate function:
Rotate1 = Rotate(FileIn1, cosd(45));
When we place a function inside of another, it is called a nested function. In both cases, we call the function, and then encase its parameters in parentheses. When we assign a variable to a function, we terminate the line with a semicolon, as is evident in both examples. Everything in between the parentheses can be formatted however you want, so the following two examples are identical:
Mult1 = Mult(FileIn1, 1,1,1);
and
Mult1 = Mult(
FileIn1,
1,
1,
1
);
Here is the coolest way of getting a function format. Create the node(s) in the interface, select and copy them with Ctrl+C, and paste it into a text editor. Otherwise,
|
Comments
You can temporally comment out lines in a script with the following symbols:
# This line is commented out
// This line is also commented out
This is not commented out //But this is
/* All of these lines are commented out */
Conditional Statements |
You can use conditional statements as you can in C. These are particularly useful in macros, where a conditional parameter is input by the user. Keep in mind that the conditional statements require you to work in the script, and can be used to manipulate complex node assignments.
|
In the interface, you can use the Select node, which allows you to switch between any number of input nodes. Jump to its documentation for more information. This strategy allows you to stay within the interface without resorting to the script. However, the difference is that Shake will only build the part of the script that it needs when doing conditional statements listed below. Select will have the overhead of its input nodes.
Finally, note that ++i or --i is supported for increments. i++ is as well, but will return a warning message.
One note: html makes the {curly brackets} largely illegible in this font. Therefore, I have colored them blue to make them obvious.
Used to express decisions. If the test expression is true, then the first statement is executed. If false (and if an "else" part exists), then the second statement is executed. If "else" is left off, Shake does nothing and moves on.
if (expression evaluates to non-zero) { do_this } else { do_this } else { do_this }
Normally, the three expressions of the "for" loop are an initialization (a=1), a relational test (a<10), and an increment (a++): for (a=1; a<10; a++). So "a" would be set to one, checked if it was less than 10, then if that was true, a statement was performed, "a" was incremented by one, the test was checked again and this cycle continued until "a" was found to be not less than 10 (untrue).
for (initialize; test; increment) { do_this }
If the given expression is true or non-zero, the following statements are performed, and the expression is reevaluated. The cycle continues until the expression becomes false.
while (this_expression_is_true) { do_this }
If there is no initialization or re-initialization, "while" often makes more sense than "for".
This variation of "while" is different in that it tests at the bottom of the loop, so the statement body is done at least once. In the "while" above, the test may be false the first time and so nothing is done. Note on all of the other statements, a semicolon was not needed. This expression does need it at the end.
do { do_this } while (this_expression_is_true);
You can build your own functions using pre-existing ones. These are typically called macros. These files can be saved into the scrip that uses them, or can be saved in a file with a .h file extension. These files are saved in your startup directory, which is located either in <ShakeDir>/include/startup, <UserDir>/nreal/include/startup, or a startup directory under a directory specified by the environment variable $NR_INCLUDE_PATH. For more information on this, jump to Customize Shake - Where to Put Preference Files. I generally use my UserDir, also known as your Home directory.
|
Here is what a macro looks like:
dataType MacroName( dataType parameterName=defaultParameterValue, dataType parameterName=defaultParameterValue, ... ) { Macro Body return variable; }
For example, this function called angle calculates the angle between two points, returning a float, using the atan2d function. How do I know this? Look at Functions By Class - Expressions under Trig functions. Notice that the default parameter value is optional, so if you use this function, all four values must be supplied:
float angle( float x1, float y1, float x2, float y2 ) { return atan2d(y2-y1,x2-x1); }
Because the macro is so simple (one function), there is no Macro Body per se; we just attach the function to the return statement, which indicates what is spit out of the macro. To use this function, I would use something like:
myAngle = angle(0,0,100,100);
which would return 45.0.
Here is an example of an image function. It adds that "vaseline-on-the-lens" effect that you usually reserve for actresses who didn't have time for makeup. If you build it in the script, it would look like this, with orig being the image you apply the function to. The LumaKey is used to extract only the highlights. These are then blurred, and then applied back onto the original image with the Screen node, which is nice for glows and reflections. The Mix is used to control how much of the original image you want to show through. The example image shows the original image on the left, and the macro result on the right. Photo courtesy of Photron.
Here are the nodes reformated as a macro. The macro parameters are bold.
image SoftGlow( image In=0, float blur=0, float lowClip=.3, float hiClip=.9, float percent=100 ) { LumaKey1 = LumaKey(In, lowClip, hiClip, 0, 0, 1); Blur1 = Blur(LumaKey1, blur, xPixels, 0, "gauss", xFilter, "rgba"); Screen1 = Screen(In, Blur1, 1); Mix1 = Mix(In, Screen1, 1, percent, "rgba"); return Mix1; }
This tells us the macro name is SoftGlow, and it pumps out an image. The parameters are expecting one image input named In. This would give you one knot at the top of the node; if you were expecting two image inputs, there would be two knots, etc. The other values are all floats, controlling the macro settings. These are assembled in the macro body, with the return statement indicating Mix1 is being output. The low and hiClip parameters determine what levels the highlights are at.
If you have saved this into a startup .h file, i.e., <UserDir>/nreal/include/startup/SoftGlow.h, it will be immediately available on the command-line. You can also find a copy of this named SoftGlow.h under docs/cook/macros.
Type
shake -help softglow
and it should return
-softglow [blur] [lowClip] [hiClip] [percent]
Names of files have nothing to do with names of macros. Only the function name is important when calling it in the script. |
If you launch the interface, your heart will sink to miserable depths of despair when you discover that it isn't in the interface. We need a separate file and set of functions to load it into the interface. These ui functions are saved in a subdirectory of startup called ui. Here is the ui code to make a button under the Filter tab with no icon that says "SoftGlow" and calls up the SoftGlow function when pressed. Save it in <ShakeDir>/include/startup/ui, <UserDir>/nreal/include/startup/ui or $NR_INCLUDE_PATH/startup/ui. You can also find a copy of this as docs/cook/macros/SoftGlowUI.h
nuiPushMenu("Tools"); nuiPushToolBox("Filter"); nuiToolBoxItem("@SoftGlow",SoftGlow()); nuiPopToolBox(); nuiPopMenu();
The @ sign is the indication that no icon is associated with it. If you had
an icon, omit the @ sign (Do not add the tab prefix or image type extension)
and save an icon 75x40 pixels in size under your icons directory (<ShakeDir>/icons
or <UserDir>/nreal/icons or $NR_ICON_PATH), naming it
Filter.SoftGlow.nri.
These icons typically have the following characteristics:
|
Now when you launch the interface, you should have a button under the Filter tab that creates the SoftGlow function. If nothing happens, look in the console window for any error messages.
Here is a list of typical errors. When diagnosing a macro, first run it in the command-line, i.e., shake -help myFunction. If nothing comes up, you know there is a problem with your startup .h file. If that works fine, move on to the interface, and read any error messages in the console window. This is your number one diagnostic tool.
Error Behavior | Probable Cause |
In the command-line, the function doesn't appear when typing shake -help myFunctionName |
|
Function does not appear in the interface |
|
In the interface, the button appears without an icon | You haven't removed the @ sign in your ui.h file. Otherwise, follow the How to Make a Macro tutorial. |
The icon appears as a dimple |
The icon can't be found.
|
The icon is fine, but nothing happens when you click on it. |
|
There are two places to set default values for your macros. The first is in the startup.h file when you declare the parameters. From the example above,
image SoftGlow( image In=0, float blur=0, float lowClip=.3, float hiClip=.9, float percent=100 ) ...
Each of these has a default value assigned to it. Note that the image has 0, meaning "no input".
These values are applied to both the command-line or the gui defaults. If you don't supply a default argument, you must enter a value when you call the function. It is therefore recommended that you enter defaults.
The second place is in the ui.h file when the function is called. You can override the startup defaults by entering your own in the ui.h file. Normally, you have something like this in your ui.h file:
nuiPushMenu("Tools"); nuiPushToolBox("Filter"); nuiToolBoxItem("@SoftGlow",SoftGlow()); nuiPopToolBox(); nuiPopMenu();
This sets new default values just for the interface:
... nuiToolBoxItem("@SoftGlow",SoftGlow(0,100,.5,1, 95)); ...
Most of Shake's functions (third-party development being the exception) are stored in two files under <ShakeDir>/include in nreal.h and nrui.h. nreal.h is the complete list of all functions and settings. The nrui.h file builds the interface. You can modify these files to suit your needs, but we strongly recommend making a backup of these files before you begin. Otherwise, if you screw up the file, you screw up Shake. Way to go. Each of these files is an excellent source for examples as well. |
Attaching Parameter Widgets |
If you peek at Customize Shake - ui Preference Files you will see that there are many different behaviors you can attach to your parameters. This section takes a raw macro and shows different examples of behavior to change the parameters sliders to more efficient widgets.
The example macro I created is called VidResize. It takes an image of any size and resizes it to video resolution. There are slider controls to decide if you want NTSC or PAL (vidformat), to keep the aspect ratio (keepAspect), and if you do keep the aspect ratio, what color is the background area that is exposed. The original tree looks like this:
You can find the macro and the ui file in doc/cook/macros. Place VidResize.h in your <UserDir>/nreal/include/startup directory, and VidResizeUI.h in the <UserDir>/nreal/include/startup/ui directory. The only tricky bits in the macro so far are the Select function and the logic to choose the y height. When the branch of Select equals 1, the first image input is passed through. When it equals 2, the second branch is fed through. Our keepAspect parameter is fed into Select to choose which branch we want. The other tricky bit is the height logic, the first line of the macro body. It declares an internal variable (it can't be seen outside the macro) named yRes. It tests vidFormat to see if it equals 0. If so, it sets yRes to equal 486, the height of an NTSC image. Otherwise, it sets yRes to 576, the standard PAL image height..
image VidResize( image In=0, int keepAspect=1, //keeps aspect ratio or not int vidFormat=0, //This select NTSC (0) or PAL (1) res float bgRed=0, //if keeping aspect ratio, determines float bgGreen=0, //the bg color float bgBlue=0 ) { curve int yRes = vidFormat==0?486:576; Fit1 = Fit(In, 720, yRes, "default", xFilter, 1); Resize1 = Resize(In, 720, yRes, "default", 0); SetBGColor1 = SetBGColor(Fit1, "rgbaz", bgRed, bgGreen, bgBlue, 0, 0 ); Select1 = Select(keepAspect, Resize1, SetBGColor1, 0, 0); return Select1; }
The ui file looks like this:
nuiPushMenu("Tools"); nuiPushToolBox("Transform"); nuiToolBoxItem("@VidResize",VidResize()); nuiPopToolBox(); nuiPopMenu();
Quite dramatic. Launch Shake to see how it works:
If you have attached the node and started playing with the VidResize node, you will notice the first irritating thing is that the keepAspect slider goes from 0 to 1. The values we want are 1 or 2. Since we don't want to have to manually enter our format, it would be better to set the slider range from 1 to 2. This is done in the ui file VidResizeUI.h:
Looking up the format in Customize Shake - ui Preference files- Assigning Slider Ranges, I see that the following line will work:
nuiPushMenu("Tools"); nuiPushToolBox("Transform"); nuiToolBoxItem("@VidResize",VidResize()); nuiPopToolBox(); nuiPopMenu(); nuiDefSlider("VidResize.keepAspect", 1, 2);
The VidResize part of VidResize.keepAspect indicates to Shake that you only want to modify the keepAspect slider in the VidResize function. If you don't specify this, i.e., just had nuiDefSlider("keepAspect", 1, 2);, it would try to apply that slider range to all parameters named keepAspect, no matter what function it is in.
Save your ui.h file and launch Shake again and test the slider range. It should go from 1 to 2 now.
If you start seeing controls on parameters that you haven't created, or if you see other functions that have odd behaviors, make sure you have specified which function receives the control. For example, if you set nuiDefSlider("depth", 1, 2); anytime Shake sees a parameter named depth (for example, if somebody makes a macro to set bit depth to something), it will take a range of 1 to 2. Therefore, make sure you preface the depth with a function name: nuiDefSlider("MyFunction.depth", 1, 2); |
Now that we are playing with it, we probably are saying to ourselves that it's pretty petty to have a value (1 or 2) indicate to the user what that slider means. It would be much better if there was simply an on/off button there. If the button is on, we want to keep the aspect ratio. If it is off, we don't want to keep the aspect ratio. Again, looking up the format in Customize Shake - ui Preference Files - Creating an On/Off button, I find what I want. I replace the slider range function, since I don't want a slider, I want an on/off button:
nuiPushMenu("Tools"); nuiPushToolBox("Transform"); nuiToolBoxItem("@VidResize",VidResize()); nuiPopToolBox(); nuiPopMenu(); nuxDefExprToggle("VidResize.keepAspect");
The only tricky thing here is that this always returns a value of 0 or 1, with 0 being off and 1 being on. I want values of 1 or 2. So what I do is edit my startup file VidResize.h file, and add 1 to my use of keepAspect in the Select function:
... SetBGColor1 = SetBGColor(Fit1, "rgbaz", bgRed, bgGreen, bgBlue, 0, 0 ); Select1 = Select(keepAspect+1, Resize1, SetBGColor1, 0, 0); return Select1; }
Now when you relaunch Shake, it should look something like this:
Picking color on a slider isn't nearly as impressive to your arch-foes as using the Color Picker, so we will attach bgRed, bgGreen, and bgBlue to a Color Picker so you can interactively pick your color. Looking it up in Customize Shake - ui Preference Files - Assigning the Color Picker, I see the following format will probably work. Back to the ui VidResizeUI.h file:
nuiPushMenu("Tools"); nuiPushToolBox("Transform"); nuiToolBoxItem("@VidResize",VidResize()); nuiPopToolBox(); nuiPopMenu(); nuxDefExprToggle("VidResize.keepAspect"); nuiPushControlGroup("VidResize.Background Color"); nuiGroupControl("VidResize.bgRed"); nuiGroupControl("VidResize.bgGreen"); nuiGroupControl("VidResize.bgBlue"); nuiPopControlGroup(); nuiPushControlWidget("VidResize.Background Color", nuiConnectColorPCtrl(kRGBToggle,kCurrentColor,1) );
This one is interesting because the first five new lines are the code to make a sub-tree. Even if we didn't add the last lines to attach the Color Picker, the three color sliders would still be organized under a subtree named Background Color.
The next three lines attach the color picker to the subtree called Background Color, and attach an RGB color picker that uses the Current color. You could also use, for an alternative example, an HSV color picker that returns an Average color, something you might use for a chromakey macro.
It should look like this in the interface:
The next thing to do is attach some button toggles to vidFormat. Again, 0 and 1 aren't terribly informative as to what precise video format one is setting, so it would be better if there were buttons to indicate what we mean. Here are two different examples.
The first button we will do is a toggle between a button that says PAL (vr_pal.off.nri) and NTSC (vr_ntsc.off.nri) when we press it. We also use two buttons (vr_pal.off.focus.nri, vr_ntsc.off.focus.nri) that will be brighter than the other two buttons to indicate when the cursor is over the button. We call these focus buttons.
Here is the code. It looks like this in Customize Shake- Creating Push-Button Toggles, and here is the ui file:
nuiPushMenu("Tools"); nuiPushToolBox("Transform"); nuiToolBoxItem("@VidResize",VidResize()); nuiPopToolBox(); nuiPopMenu(); nuxDefExprToggle("VidResize.keepAspect"); nuiPushControlGroup("VidResize.Background Color"); nuiGroupControl("VidResize.bgRed"); nuiGroupControl("VidResize.bgGreen"); nuiGroupControl("VidResize.bgBlue"); nuiPopControlGroup(); nuiPushControlWidget("VidResize.Background Color", nuiConnectColorPCtrl(kRGBToggle,kCurrentColor,1) ); nuxDefExprToggle("VidResize.vidFormat", "ux/vr_ntsc.off.nri|ux/vr_ntsc.off.focus.nri", "ux/vr_pal.off.nri|ux/vr_pal.off.focus.nri" );
The new lines list the normal button, followed by the focus button. The icons directory will automatically be scanned, but notice we use the ux subdirectory. The value returned will always be 0 for the first entry, 1 for the next entry, 2 for the third entry, etc. You can place as many entries as you want.
The next version of buttons we can use is a version with radio buttons. These are similar to toggle buttons, except that you see all available buttons simultaneously. This code lists only one button name. Shake will automatically assume there is an on, on.focus, off, and off.focus version of each button in the directory you specify. If you copied the vr_ buttons earlier, you will indeed have all of these buttons. The code Customize Shake - Creating Radio Buttons, and the file looks like this:
nuiPushMenu("Tools"); nuiPushToolBox("Transform"); nuiToolBoxItem("@VidResize",VidResize()); nuiPopToolBox(); nuiPopMenu(); nuxDefExprToggle("VidResize.keepAspect"); nuiPushControlGroup("VidResize.Background Color"); nuiGroupControl("VidResize.bgRed"); nuiGroupControl("VidResize.bgGreen"); nuiGroupControl("VidResize.bgBlue"); nuiPopControlGroup(); nuiPushControlWidget("VidResize.Background Color", nuiConnectColorPCtrl(kRGBToggle,kCurrentColor,1) ); nuxDefRadioBtnCtrl( "VidResize.vidFormat", 1, 1, 0, "0|ux/vr_ntsc", "1|ux/vr_pal" );
The function looks like this in the interface:
The numbers immediately to the left of the icon listing, i.e., the 0 in "0|ux/vr_ntsc", is the value returned when that button is pressed. The nice thing about ratio buttons is they can return strings, float, or int. For example, the channel parameter in KeyMix is selecting the channel to do the masking, with R,G,B, or A returned, all strings. Because our macro VidResize only understands 0 or 1 to be meaningful, we use 0 and 1 as our return values.
There is a function I use to create these buttons. That's right, I use a macro to help make my macros. In doc/cook/macros, copy tradiobutton.h to your startup directory. In the command-line, type something like: shake -tradio "NTSC size" 79 vr_ntsc $HOME/nreal/icons/ux -t 1-4 -v This will create four buttons named vr_ntsc.on.nri, vr_ntsc.on.focus.nri, vr_ntsc.off.nri, and vr_ntsc.off.focus.nri in $HOME/nreal/icons/ux each 77 pixels wide (I know, the length given is 2 pixels wider than the actual length. I never did fix that...), and each one says NTSC size. You have to do it in the command-line because at the moment the FileOut doesn't work in a macro unless it's the command-line, and frames 1-4 create the four different files. |
Even though the radio buttons are pretty cool, another frequent option is a pop-up menu. The pop-up menu is good for when you have far more choices than will fit in a standard Parameters tab. The only catch is that they return strings, not numbers, so you have to add some logic in your macro to interpret the strings. Here is the ui code in Customize Shake - Creating a Pop-Up menu, and here is the ui code:
nuiPushMenu("Tools"); nuiPushToolBox("Transform"); nuiToolBoxItem("@VidResize",VidResize()); nuiPopToolBox(); nuiPopMenu(); nuxDefExprToggle("VidResize.keepAspect"); nuiPushControlGroup("VidResize.Background Color"); nuiGroupControl("VidResize.bgRed"); nuiGroupControl("VidResize.bgGreen"); nuiGroupControl("VidResize.bgBlue"); nuiPopControlGroup(); nuiPushControlWidget("VidResize.Background Color", nuiConnectColorPCtrl(kRGBToggle,kCurrentColor,1) ); nuxDefMultiChoice("VidResize.vidFormat", "NTSC|PAL");
You can add as many entries as you want, and each entry is separated by the | symbol. Here is the problem with pop-up menus. As mentioned before, they only return the word you have entered. Therefore, we have to change the startup macro file. In VidResize.h in the startup directory, change vidFormat to be a string instead of an integer, placing its default parameter as either NTSC or PAL, both in quotes. We also use an if/else statement below it to evaluate vidFormat and assign the proper yRes value based on vidFormat's value.
image VidResize( image In=0, int keepAspect=1, //keeps aspect ratio or not string vidFormat="NTSC", //This select NTSC or PAL res float bgRed=0, //if keeping aspect ratio, determines float bgGreen=0, //the bg color float bgBlue=0 ) { if (vidFormat=="NTSC"){ yRes=486; } else { yRes=576; } // curve int yRes = vidFormat==0?486:576; Fit1 = Fit(In, 720, yRes, "default", xFilter, 1); Resize1 = Resize(In, 720, yRes, "default", 0); SetBGColor1 = SetBGColor(Fit1, "rgbaz", bgRed, bgGreen, bgBlue, 0,0 ); Select1 = Select(keepAspect+1, Resize1, SetBGColor1, 0, 0); return Select1; }
In the interface, it will look like this:
Note that we could have avoided the use of the if/else statement and used something similar to what we had before:
curve int yRes = vidFormat=="NTSC"?486:576;
The reason I used the if/else is that usually you have multiple entries on your pop-up menu, and the conditional expression gets a bit unwieldy; so it's better to create a long if/else statement. Unwieldy, like that run-on sentence.
Frequently-Used Script Commands, Routines, and Variables |
Script Controls | Command-line equivalent | Notes |
SetTimeRange("1-100"); | -t 1-100 | The time range, i.e., frames 1-200. Note this is always in quotes. |
SetFieldRendering(1): | -fldr 1 |
Field Rendering. 0 = off |
SetFps(24); | -fps 24 | Frames per second. |
SetMotionBlur(1, 1,0); | -motion 1 1 , -shutter 1 1 | Your three global motion blur parameters |
SetQuality(1); | -fast 0 or 1 | low (0) or high (1) quality. |
SetProxyScale(1,1); | -proxyscale 1 1 |
Proxy scale and ratio for the script. See Overview - About Proxies |
SetPixelScale(1,1): | -pixelscale 1 1 | Pixel scale and ratio for the script. See Overview - About Proxies |
SetVideoResolution("NTSC 640x480") | An obsolete description of the format. | |
SetDefaultWidth(720); |
If a node goes to black or if you create an Image node like RGrad, it will take this resolution by default. | |
SetFormat("Custom") |
This will set your defaults automatically for resolution, aspect,
from the pre-created list of formats. These formats are stored
in your startup.h file in the following format: DefFormatType("Name", width, height, aspectRatio,
framesPerSecond, fieldRenderign); |
Examples |
Here are several examples of macros. Many of these can be found in the Cookbook, but they aren't installed by default.
This macro resizes an image to fit a specified size along 1 dimension. I use images for the documentation that are 300, 240 or 166 pixels wide. When I do a screen grab, I don't always know the height. With AutoFit, I can just tell Shake to resize the image down to 166 pixels wide, and to calculate the height automatically. For example, the doc/pix/uboat.iff image is 640x480 pixels. I want to calculate it down to 166 pixels wide. I don't know what number the height should be, and I don't know the exact ratio of 640 to 166. Therefore, I can just use AutoFit:
shake uboat.iff -autofit 166 h
This will calculate an image that is 166x125 pixels.
Here is the code:
image AutoFit(image img=0, int resolution=166, int calculate=1) { curve w=0; curve h=1; return Select(calculate+1, Resize(img,resolution*width/height,resolution), Resize(img,resolution,resolution*height/width) ); }
The first line creates the parameters. Note that calculate
is expecting an integer. The next two lines assign a value to both w
and h. This way, the user can type in 0 or 1 to calculate width or height,
or enter w or h, which is easier to remember. Since we assign
values to w and h, Shake will be able to determine which axis
to calculate. The return statement uses a Select to read the value
of calculate, add 1 to it, and then choose either branch 1 or 2. The
two select branches are Resize commands, which calculate the appropriate
resolution. Resize is used as an embedded function here. Also, we won't
be changing the value of w or h during our processing, so I didn't
have to declare them as curve int type of data.
This function generates a random letter up to a certain frame, at which point a static letter appears. You can select the color, switch frame, position, and size of the letter, as well as font. This function uses the standard C stringf function to pick a random letter, assigning it to the variable rdLetter. This is pumped into the Text function, which uses a conditional expression to evaluate if the current time is before the staticFrame time (set to frame 10 by default)
image RandomLetter( int width=720, int height=486, int bytes=1, const char * letter="A", int staticFrame=10, float seed=time, const char * font="Courier", float xFontScale=100, float yFontScale=100, float xPos=width/2, float yPos=height/2, float red=1, float green=1, float blue=1, float alpha=1 ) { curve string rdLetter = stringf("%c", 'A'+(int)floor(rnd1d(seed,time)*26)); return Text( width, height, bytes, time < staticFrame?"{rdLetter}":"{letter}", font, xFontScale, yFontScale, 1, xPos, yPos, 0, 2, 2, red, green, blue, alpha, 0, 0, 0, 45 ); }
This is an excerpt from the TRadioButton function which I use to generate radio buttons for the interface. The radio button code needs four icons to support it: name.on.nri, name.on.focus.nri, name.off.nri, and name.off.focus.nri. Since it is tedious to write out four separate files, I automatically change the file extensions over time with this excerpt from the macro:
image TRadioButton( const char *text="linear", ... string fileName=text, string filePath="D:/Shake2/icons/ux/radio/", int branch=time, .. ) { curve string fileState = branch == 1 ? ".on.nri" : branch == 2 ? ".on.focus.nri" : branch == 3 ? ".off.nri" : ".off.focus.nri"; curve string fileOutName = filePath + "/"+ fileName + fileState; ... return FileOut(Saturation1, fileOutName);
Since you typically calculate this with a command like this:
shake -tradio "HelloWorld" -t 1-4
it will generate HelloWorld.on.nri at frame 1, HelloWorld.on.focus.nri at frame 2, etc.
This little trick takes a string of letters and prints it out, one letter at a time. It declares a variable within the string section of a Text node:
Text1 = Text(720, 486, 1, {{ string logo = "My Logo Here"; stringf("%c", logo[(int) clamp(time-1, 0,strlen(logo))]) }}, "Courier", 100, xFontScale, 1, width/2, height/2, 0, 2, 2, 1, 1, 1, 1, 0, 0, 0, 45);
If you happen to be at a UNIX machine, you can type man strlen or man stringf and see the 1 million or so string functions available. This one uses strlen to determine the length of the string and extract the letter corresponding to the current frame.
This eager little thing lays out a clock face. We use a for loop to count from 1 to 12, printing the number into a Text function using a stringf function. I know, we should just be able to print the value of count with {count}, but it doesn't work. Go figure. We also use the cosd and sind functions to calculate the position of the number on the clock face. Keep in mind that zero degrees in Shake points East, as it does in all Cartesian math. This is called Clock, and can be found in doc/cook/macros. The falloffRadius serves no purpose in the function except to complete the on-screen control set to give us a center and a radius:
image Clock( image In=0, float xCenter=width/2, float yCenter=height/2, float radius=150, float falloffRadius=0 ) { NumComp=Black(In.width,In.height,1); for (int count=1;count<=12;++count) { NumComp = Over( Text(In.width, In.height, 1, {{ stringf("%d",count) }}, "Courier", radius*.25, xFontScale, 1, cosd(60+(count-1)*-30)*radius+xCenter, sind(60+(count-1)*-30)*radius+yCenter, 0, 2, 2, 1, 1, 1, 1, 0, 0, 0, 45), NumComp ); } return Over(NumComp,In); }
This function can be used to extract the file name from a FileOut or FileIn function so you can print it on a slate. You would use it in a text function.
const char *getBaseFilename(const char *fileName) { extern const char * strrchr(const char *, char); const char *baseName = strrchr(fileName, '/'); return baseName ? baseName+1 : fileName; }
To use it, place in the text parameter of a Text function a line like this:
{getBaseFilename(FileIn1.imageName)}
This allows you to take an image and tile it out into rows and
columns, similar to the Tile function in the Other tab. However,
this one also allows you randomly move each tile, as well as scale the tiles
down. The random movement is generated with the turbulence function (see
Functions By Class
- Expressions) Because of this, it is less efficient than the Tile
function, which can be seen by looking at the <ShakeDir>/include/nreal.h
file.
image TileExample( image In=0, int xTile=3, int yTile=3, float angle=0, float xScale=1, float yScale=1, float random=30, float frequency=10 ) { result = Black(In.width, In.height, In.bytes); curve float xFactor=(In.width/xTile)*.5; curve float yFactor=(In.height/yTile)*.5; for (int rows=1; rows<=yTile; ++rows){ for (int cols=1; cols<=xTile; ++cols){ Move2D1 = Move2D(In, -width/2+xFactor+((cols-1)*xFactor*2)+ (turbulence(time+(cols+1)*(rows+1),frequency)-.5)*random, -height/2+yFactor+((rows-1)*yFactor*2)+ (turbulence(time+(cols+1)/(rows+1),frequency)-.5)*random, angle, 1, 1.0/xTile*xScale, 1.0/yTile*yScale ); result=Over(Move2D1,result); } } return result; }