I had a nice post started, but then when I tried to implement it, I discovered something annoying: Even if you set the Reference Size in a Transform, as soon as you put an expression on the Point control, you're back to normalized coordinates. So you'll always need to do the conversion into and out of normalized coordinates yourself.
So let's assume that you're making a layout out of images of various sizes, and you just want to stack them vertically with a gap control. In this example, I'm just going to use some Background and Scale tools to represent the incoming images. I'll designate them A, B and C. By using Scale instead of Transform, the images and canvas get resized together, so you don't need to extract the Domain of Definition (DoD) with an auto-auto-crop.
To start, we'll Merge each image over a common Background (which I'll name Canvas) of known size (3840x2160), with the Transforms after the Merge, then Merge the whole set together at the end. I'm using the Merge over Background instead of Crops here so the entire thing can respond to the Use Frame Format settings if desired.
We can get the dimensions of the incoming images by querying the Output of the associated Scale node: ScaleA.Output.OriginalWidth and ScaleA.Output.OriginalHeight, for instance. The thing to remember is that the Transforms always measure from the
center of the raster, not the lower-left corner, so when setting a position, we'll need to use half the height.
To convert from pixels to normalized coordinates, simply divide the pixel dimensions by Canvas.Output.OriginalHeight. To get a pixel distance, multiply that expression by the normalized coordinates.
I'm going to place the images with A at the top, B below it, then C below that, then center the entire composition. Since I want it centered, it's tempting to start with B and just let it stay where it is, but each item is a different height, so it may not end up actually in the center of the screen. So I'll instead start from the bottom because addition is easier on my brain than subtraction.
To place the first object at the bottom of the screen, I use this expression for the Center control of TransformC:
Point(0.5, (ScaleC.Output.OriginalHeight/2) / Canvas.Output.OriginalHeight)
Now, I can change the Scale of the element or the height of the Canvas, and it will always stick to the bottom edge. Next, element B. I want that one to stick to the top of Element C, so it gets this expression:
Point(0.5, (ScaleC.Output.OriginalHeight + ScaleB.Output.OriginalHeight/2) / Canvas.Output.OriginalHeight)
Note that I'm using the full height of element C—I need the distance from the bottom of the screen, plus half the height of element B. Element A gets the same treatment: half its height plus the full height of each object below it:
Point(0.5, (ScaleC.Output.OriginalHeight + ScaleB.Output.OriginalHeight+ ScaleA.Output.OriginalHeight/2) / Canvas.Output.OriginalHeight)
Now let's get some padding in there. For flexibility, we'll distinguish between padding between elements and the margin between the edge of the frame and the composition. This is pretty easy. All we really need is a slider to set the parameters and some simple addition in each expression. At first, I used a CustomTool for its screw controls, but they're not very responsive, so instead I'm going to use the Edit Controls feature to add the slider to the Canvas node.
We don't need padding on TransformC, but we'll add it to TransformB and
twice to Transform A:
Point(0.5, (ScaleC.Output.OriginalHeight + Canvas.Padding*2 + ScaleB.Output.OriginalHeight+ ScaleA.Output.OriginalHeight/2) / Canvas.Output.OriginalHeight)
Now I'll Crop the entire composition to the new height, adding a Crop node after my last Merge. Crop operates from the lower left corner of the image (0,0), so if I leave the Offsets alone, I can calculate the total height by adding up all the elements' heights and adding the padding. Then I need only add twice the margin value (accounting for both top and bottom), then Offset Y by -Margin to arrive at the final frame.
And here's the entire setup:
- Code: Select all
{
Tools = ordered() {
A = Background {
NameSet = true,
Inputs = {
Width = Input { Value = 640, },
Height = Input { Value = 480, },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
TopLeftRed = Input { Value = 0.551, },
},
ViewInfo = OperatorInfo { Pos = { 660, -181.5 } },
},
ScaleA = Scale {
NameSet = true,
Inputs = {
XSize = Input { Value = 1.074, },
HiQOnly = Input { Value = 0, },
PixelAspect = Input { Value = { 1, 1 }, },
Input = Input {
SourceOp = "A",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 770, -181.5 } },
},
ScaleB = Scale {
NameSet = true,
Inputs = {
HiQOnly = Input { Value = 0, },
PixelAspect = Input { Value = { 1, 1 }, },
Input = Input {
SourceOp = "B",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 770, -148.5 } },
},
B = Background {
NameSet = true,
Inputs = {
Width = Input { Value = 290, },
Height = Input { Value = 743, },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
TopLeftGreen = Input { Value = 0.52, },
},
ViewInfo = OperatorInfo { Pos = { 660, -148.5 } },
},
C = Background {
NameSet = true,
Inputs = {
Width = Input { Value = 839, },
Height = Input { Value = 421, },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
TopLeftBlue = Input { Value = 0.52, },
},
ViewInfo = OperatorInfo { Pos = { 660, -115.5 } },
},
ScaleC = Scale {
NameSet = true,
Inputs = {
HiQOnly = Input { Value = 0, },
PixelAspect = Input { Value = { 1, 1 }, },
Input = Input {
SourceOp = "C",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 770, -115.5 } },
},
Merge3 = Merge {
Inputs = {
Background = Input {
SourceOp = "Canvas",
Source = "Output",
},
Foreground = Input {
SourceOp = "ScaleA",
Source = "Output",
},
PerformDepthMerge = Input { Value = 0, },
},
ViewInfo = OperatorInfo { Pos = { 880, -181.5 } },
},
Merge5 = Merge {
Inputs = {
Background = Input {
SourceOp = "Canvas",
Source = "Output",
},
Foreground = Input {
SourceOp = "ScaleC",
Source = "Output",
},
PerformDepthMerge = Input { Value = 0, },
},
ViewInfo = OperatorInfo { Pos = { 880, -115.5 } },
},
Merge4 = Merge {
Inputs = {
Background = Input {
SourceOp = "Canvas",
Source = "Output",
},
Foreground = Input {
SourceOp = "ScaleB",
Source = "Output",
},
PerformDepthMerge = Input { Value = 0, },
},
ViewInfo = OperatorInfo { Pos = { 880, -148.5 } },
},
TransformA = Transform {
NameSet = true,
Inputs = {
Center = Input { Expression = "Point(0.5, 0+(ScaleC.Output.OriginalHeight + Canvas.Padding*2 + ScaleB.Output.OriginalHeight+ ScaleA.Output.OriginalHeight/2) / Canvas.Output.OriginalHeight)", },
ReferenceSize = Input { Value = 1, },
Input = Input {
SourceOp = "Merge3",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 990, -181.5 } },
},
TransformC = Transform {
NameSet = true,
Inputs = {
Center = Input { Expression = "Point(0.5, 0+(ScaleC.Output.OriginalHeight/2) / Canvas.Output.OriginalHeight)", },
ReferenceSize = Input { Value = 1, },
Input = Input {
SourceOp = "Merge5",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 990, -115.5 } },
},
TransformB = Transform {
NameSet = true,
Inputs = {
Center = Input { Expression = "Point(0.5, 0+(ScaleC.Output.OriginalHeight + Canvas.Padding + ScaleB.Output.OriginalHeight/2) / Canvas.Output.OriginalHeight)", },
ReferenceSize = Input { Value = 1, },
Input = Input {
SourceOp = "Merge4",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 990, -148.5 } },
},
Merge2 = Merge {
Inputs = {
Background = Input {
SourceOp = "TransformC",
Source = "Output",
},
Foreground = Input {
SourceOp = "Merge1",
Source = "Output",
},
PerformDepthMerge = Input { Value = 0, },
},
ViewInfo = OperatorInfo { Pos = { 1100, -115.5 } },
},
Canvas = Background {
NameSet = true,
Inputs = {
Width = Input { Value = 3840, },
Height = Input { Value = 2160, },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
TopLeftAlpha = Input { Value = 0, },
Padding = Input { Value = 100, },
Margin = Input { Value = 80, },
},
ViewInfo = OperatorInfo { Pos = { 880, -49.5 } },
UserControls = ordered() {
Padding = {
LINKS_Name = "Padding",
LINKID_DataType = "Number",
INPID_InputControl = "ScrewControl",
INP_Default = 0,
INP_Integer = true,
INP_MinScale = 0,
INP_MaxScale = 1920,
ICS_ControlPage = "Image",
},
Margin = {
LINKS_Name = "Margin",
LINKID_DataType = "Number",
INPID_InputControl = "ScrewControl",
INP_Default = 0,
INP_Integer = true,
INP_MinScale = 0,
INP_MaxScale = 1920,
ICS_ControlPage = "Image",
}
}
},
Merge1 = Merge {
Inputs = {
Background = Input {
SourceOp = "TransformB",
Source = "Output",
},
Foreground = Input {
SourceOp = "TransformA",
Source = "Output",
},
PerformDepthMerge = Input { Value = 0, },
},
ViewInfo = OperatorInfo { Pos = { 1100, -148.5 } },
},
Crop2 = Crop {
Inputs = {
YOffset = Input {
Value = -80,
Expression = "-Canvas.Margin",
},
XSize = Input { Value = 3840, },
YSize = Input {
Value = 2040,
Expression = "Canvas.Margin+ScaleC.Output.OriginalHeight+Canvas.Padding+ScaleB.Output.OriginalHeight+Canvas.Padding+ScaleA.Output.OriginalHeight+Canvas.Margin",
},
Input = Input {
SourceOp = "Merge2",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 1210, -115.5 } },
}
}
}
Now, what might be interesting is to find a way to do this dynamically, so it responds to any number of input images. It could possibly be done with a macro, but you'd have to arbitrarily select the maximum number of images and then find a way to filter for unconnected inputs.