Creative Coding With DCTL: Part 1

May 25, 2021

Cullen Kelly kicks off a new exciting series about DCTL coding. Learn the vocabulary, syntax, and techniques to create DCTLs to aid your color grading.


Series

 A Step By Step 10-Part Series To Create A Creative DCTL

Welcome to the first installment of Creative Coding With DCTL!

Over the course of this ten-part series, we’re going to learn what DCTL is, how it works, and how we can use it to develop custom creative tools.

At the end of this series, you’ll walk away with not only a new skill set, but a fully functional ‘Filmic Contrast’ tool that allows a user to dial film-print-like contrast into their image, and to push a custom cocktail of blue/green into the shadows and red/green into the highlights.

In our first few installments, we’ll be focusing on the core concepts needed to begin coding. While it may be tempting to skip ahead to the hands-on lessons, take my word for it as a self-taught coder that these concepts are critical to successful and pleasurable development, and it’s far easier to learn them now than when you’re staring down a few hundred lines of misbehaving code with no clue how to troubleshoot them.

We’ve got a lot to cover, so let’s dive in!

What Is DCTL?

  • DCTL, or the Davinci Color Transform Language, is a programming language created by Blackmagic Design that allows Resolve users to perform mathematical operations on image data within the Resolve ecosystem. It’s based on CTL (Color Transform Language), which was developed for a similar purpose, albeit not specific to Resolve.
  • DCTL is what’s known as a high-level language, meaning it’s reasonably easy to read and write — at least in contrast to low-level programming languages such as assembly language. However, like all programming languages, DCTL has highly specific conventions and syntax, and even small deviations from these will usually result in errors.

__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B)

{

float3 rgb = {0.f, 0.f, 0.f};

return rgb;

}
Above is a simple DCTL script that returns solid black pixels regardless of input.
org 0x100
mov dx, msg
mov ah, 9
int 0x21
mov ah, 0x4c
int 0x21
msg db 'Hello World!', 0x0d, 0x0a, '$' ;-terminated message

An assembly language “Hello World!” program.

  • DCTL code is written in a plain text editor,  then saved with a .dctl extension so that Resolve can recognize and use it. A functional chunk of code saved in this way is known as a script, program, or most commonly, a DCTL. It’s worth noting that most of the time, when you hear DCTL mentioned, people are talking about specific scripts written in DCTL, as opposed to the programming language itself.

What Can DCTL Do?

In a broad sense, DCTL has only one capability: the continuous input, manipulation, and output of pixel data inside of Resolve. It can also present one or more UI elements to the user (such as sliders, drop-down menus, and checkboxes), and incorporate the user’s choices into its manipulation.

Here are a few examples of tasks we might perform with a DCTL:

  • Applying lift, gamma, gain, and/or offset to an image based on user input
  • Generating a solid color and completely overwriting the input image
  • Applying a LUT

What Can’t DCTL Do?

While the above is a fully comprehensive definition of DCTL’s capabilities, it’s worth singling out out some of the functions that don’t fall under that umbrella:

  • Outputting anything other than pixel data back to Resolve. It’s not possible to write metadata, save files, or output text.
  • Computationally expensive operations. This is not a hard limitation, but because DCTL continuously processes every pixel, there’s a level of complexity past which performance will get sluggish and your system may become unstable. Long story short, it’s entirely possible to write a “valid” DCTL that will freeze or crash your system.

What Do I Need To Build A DCTL?

Here’s what you’ll need to follow along with this series:

  • Davinci Resolve Studio (the free version of Resolve doesn’t support DCTLs).
  • A plaintext editor — I recommend Sublime Text
  • A basic grasp of arithmetic, including power functions and logarithms. We’re going to do a refresh on these, but you should know upfront there’s no way to avoid math when creating DCTLs.
  • A clear concept of what you’d like to build. This is vital to successful DCTL development. New coders struggling to realize a tool often get fixated on what’s wrong with their code, but far more often their problem is a fuzzy concept. 
  • A process-oriented mindset. Another inevitable reality of coding is that it takes time. The process is never linear and typically involves multiple detours and roadblocks. We have to learn to accept and enjoy this process, rather than seeing it as a barrier to our result.
  • Optional: a Github account and a copy of Github Desktop. This isn’t strictly necessary, but it’s the way I’ll be publishing code throughout this course, and it’s a great platform for sharing, debugging, and tracking changes to your scripts.

Now that we’ve answered these basic questions, let’s get acquainted with the key building blocks of any DCTL.

Comments

Comments are an important part of the development process — they allow us to annotate questions, create to-dos, and describe the function of things in plain English. In DCTL, any line which starts with // will be ignored, and any text falling within /* and */ will be ignored, regardless of the number of lines.
// This may look interesting, but it won't do anything

/*

This may look even more interesting, but it also won't do anything.

No, really.

Nothing at all.

*/

Variables

Variables are the most basic element of a DCTL. Without them, we couldn’t store, manipulate, or return pixel data. They are ‘declared’ into existence by the programmer and identified by a unique name of the programmer’s choosing which describes how they’ll be used. This declaration also includes the designation of a type, which in DCTL falls into one of the following categories:

  • floats are floating-point numbers,  meaning numbers that can have many decimal points of precision. They are by far the most common variable type used in DCTL.
  • ints are integers, meaning whole numbers.
  • chars are character strings, meaning text which won’t be used in mathematical operations. They are seldom used in DCTL.

In DCTL, we can create as many variables as we like, and modify them however we like, as many times as we like. The only exceptions are for the reserved variables which are a part of our script the moment we set it up. These include variables such as p_R, p_G, and p_B, which are floats representing the value of the incoming pixel’s red, green, and blue channels.

Variables are case-sensitive, cannot begin with a numeral, and cannot contain spaces.
int one = 1;

float pi = 3.14159;

char favorite_food = "eggs";
Above are some example declarations of an int, float, and char variable.

Finally, variable declarations, like virtually any single-line operation in DCTL, must end with a semi-colon. This is by far the easiest syntax error to make when coding — miss a semi-colon, and your script won’t run.

Functions

As with most programming languages, a function is the basic unit of action in any DCTL. It contains a repeatable set of instructions that can be invoked once or multiple times and is identified by a unique name chosen by the programmer to describe what it does.

For example, if I were to write my morning routine as a DCTL, I might have a function called get_dressed, another called brush_teeth, another called fix_breakfast, etc.

In DCTL, a function can take one or multiple input variables and must return a single output variable.

Just as with variables, functions must be declared, and each of their input and output variables must be given a type and a case-sensitive name.
__DEVICE__ float addtwo(float in)

{

out = in + 2;

return out;

}
Here’s an example of a function which adds 2 to the input.

All DCTLs must have at least one function in order to run, and this core function must be named ‘transform’ and be declared with specific input variables.
__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B)
Above is one of the three acceptable forms of declaration for DCTL’s mandatory ‘transform’ function (more on this later). The ‘transform’ function is always run first in a DCTL.

The return command can be invoked anywhere within a function and will cause the function to stop running and return the specified value.

Arrays

Arrays are a bundle of two or more individual variables of the same type. The individual variables that make up an array are called elements, and an array can contain as many elements as needed. In DCTL, arrays containing a total of three elements are by far the most common, as they allow us to store pixel data for an image’s red, green, and blue channels simultaneously. We can use float3 to declare a three-element array of floats:
float3 color = {1.f, 0.f, 0.f};
Note that when we set the values of an array’s elements, we use braces and commas to enclose and separate them.

We can retrieve and alter the value of individual elements within a float3 using .x, .y, and .z, like so:
float element1 = color.x; // set the value of element1 to the value of color's first element

float element2 = color.y; // set the value of element1 to the value of color's second element

float element3 = color.z; // set the value of element1 to the value of color's third element

color.x = 0.f; // set the value of color's first element to 0
color.y = 0.5f; // set the value of color's second element to .5
color.z = 0.33f; // set the value of color's third element to .33

Manipulating Variable Values

We’ve already seen several examples of setting and altering the value of a variable, but to make it explicit, we do this by placing an equal sign to the right of the variable name, followed by the value we wish it to take on. This can be an explicit number or string, or it can be another variable. It’s also how we call a function into action, for example:
float number = 3.5f;

number = addtwo(number); // Use the addtwo function to change the value of number from 3.5 to 5.5 (3.5 + 2 = 5)
We can also perform arithmetic with this method:
number = number + 1.f // Add 1 to the value of number

number = number - 5.f // Subtract 5 from the value of number

number = number / 2.f // Divide number by 2

number = number * 3.f // Multiply number by 3
You may also have noticed by now that when setting the value of floats, I always add a tailing ‘.f’ to the number — this is a convention to indicate to the DCTL parser that this number has floating-point precision and that I’m simply not writing out all the trailing zeros.

The Home Stretch

Ok, we’ve just about covered the fundamental properties and building blocks of DCTL. Let’s wrap things up with a video tour of the UI options available to us within DCTL, and a preview of our final Filmic Contrast tool in action.

Thanks for joining me for this first installment! Feel free to leave your questions below.

-Cullen


Comments

Hundreds of Free Tutorials

Get full access to our entire library of 900+ color tutorials for an entire week!


Start Your Free Trial
Loading...