Creative Communities of the World Forums

The peer to peer support community for media production professionals.

Activity Forums Adobe After Effects Expressions Dynamic Text Positioning/Scaling

  • Dynamic Text Positioning/Scaling

    Posted by Andy Kreutzberg on August 23, 2012 at 2:41 pm

    Hello all,

    this is my first question for this fine community. I consider myself a beginner with expressions. I know some of the basics and already managed to solve some problems on my own by finding workarounds, etc. Now here is the problem that i can’t find a sufficient workaround for: I am producing templates for onair graphics that editors use to put text on their inhouse-edited films. We have a certain CI for end credits, which is divided as follows:

    main credit line 1
    main credit line 2
    main credit line 3

    single credit block 1 line 1 single credit block 2 line 1 single credit block 3 line 1
    single credit block 1 line 2 single credit block 2 line 2 single credit block 3 line 2
    single credit block 1 line 3 single credit block 2 line 3 single credit block 3 line 3
    single credit block 1 line 4 single credit block 2 line 4 single credit block 3 line 4
    single credit block 1 line 5 single credit block 2 line 5 single credit block 3 line 5

    edit: there is some free space between the blocks. The forum erases that, so just think of it as three blocks of text. I will add a picture soon for better understanding.

    Main credit line 1-3 is an individual text layer, just as every block is an individual text layer.
    There is nine blocks in total, which fade from one another, so block 1, 2 ,3 are fading to block 4,5,6 etc (but thats not important as i managed to automatize the fading based on amount of credits already).
    The lowest line 5 of these blocks is about aligned to the inner title safe line.

    Maybe the problem(s) with this layout are already clear just by looking at this. Imagine your text for one of these blocks or even lines is very long. It will overlap with the line of the block to the right side or go out of frame entirely. Adding more lines (say 5-6) will lead to the text going out of the frame eventually.

    I developed a template to scale and move the text according to controllers. You would have a slider control that says “scale of font” that scales all of these blocks equally or a “number of lines” slider that would hide lines automatically with subtract masks or move the text up when lines are added. All this made things easier already. But the editors want it even more simpler and just copy paste infos. I would have to automatize the scaling and positioning based on the amount of characters, or better saying on the size of the text’s bounding box.

    Here is what should happen: at the point that one of the block’s lines would intersect with its neighbor to the right, it would start scaling the font for all blocks down, while also moving all blocks as well as the main credit lines up and down, depending on the amount of lines and size of text for the largest block. I hope its clear enough what i am aiming for. After you copy paste the infos, the blocks would be aligned perfectly without crossing over, going out of frame or loosing their relative spacing to the main credit line (so this one must move up and down with them).

    I have spent hours trying to understand the sampleImage methods provided by Dan Ebberts and others in here: https://aenhancers.com/viewtopic.php?f=6&t=939&view=previous. I know the key is in there somewhere. Problem is, all of these are very slow to compute for each credit block or they are not compatible for some reason with a resolution larger than 1024×576… plus they are hard to modify for a expressions beginner like myself. I was only able to adjust the second version on that link, so that it moves a single credit and scales it based on width and height information, but this expression is too slow to use for all blocks plus main credit line. I already found a way to narrow all values of all blocks to the largest one but its worthless without determining the length and height information from the text itself in an efficient way. Is sampleImage the best way? I would imagine maybe not sampleImage-ing every block but all at once somehow (all expressions i found only sample one text layer)? Or is there an alternative? Is it even possible at all?

    I have reached a point where i am pretty lost and hope you guys can help me out.

    regards,
    Andy

    Dan Ebberts replied 13 years, 7 months ago 2 Members · 13 Replies
  • 13 Replies
  • Dan Ebberts

    August 23, 2012 at 9:07 pm

    I think the only possibility is a script, which (unlike expressions) can actaully read the extents of text layers. Of course, you have to take action to run a script–it wouldn’t be a simple as copy and paste. Maybe your operators paste in all their text and then run the script to clean things up. It should be do-able, but scripting is a little trickier than expressions.

    Dan

  • Andy Kreutzberg

    August 27, 2012 at 9:30 am

    Hey Dan, thanks for your reply. Unfortunately, with scripting i think i would have to start at zero again. Although time is not an issue, i don’t think i can get such a complex script working in a considerable amount of time. Unless you can point me to a direction where to start maybe.

    But i thought about the whole matter again and i think i know exactly what kind of expression could get things going:
    Have a look at the expressions posted on https://aenhancers.com/viewtopic.php?f=6&t=939&view=previous again. The second expression on that page posted by nab on Mon Feb 11, 2008 7:27 pm does exactly what i need. It gives you width and height data for the text’s bounding box. However, this one is really slow to compute. nab posted another one on Sat Feb 16, 2008 1:39 pm, which is much faster to compute. However, when i am opening his example project and resize it, the expression breaks plus it gives you just corner position informations of the bounding box instead of width and height.

    What i would need is a combination of the two: One that executes as fast as the Sat Feb 16, 2008 1:39 pm one in that topic but gives you width and height of the text bounding box in a full HD 1920 x 1080 comp. I know i am probably asking for a lot here, but if Dan or anyone could give such an expression a try it would solve a lot of problems. As i said i was able to adjust one of the expressions already but i am not able at all to adjust the last one despite giving it lots of trying, changing values and testing things, etc but i don’t even get it to work with a different resolution. It might also help if someone could explain the code of the Sat Feb 16, 2008 1:39 pm expression. My main problem really is that i don’t have a clue what the code of that expression does and especially how it gets its resolution for the sampleimage.

    Any help with this is hugely appreciated.

  • Dan Ebberts

    August 27, 2012 at 1:26 pm

    OK – here’s a different approach that you might be able to customize to your needs. It’s set up to assume left-justified text. It starts at the text’s left edge and moves right until it finds xTolerance blank columns of pixels. You need to set this large enought to match your text, so that, for example, it will skip over the space between adjacent words. It does a similar thing for the y direction. yTolerance can be smaller–it just needs to be big enough to include the space between a lower case i and its dot, for example. It’s ugly, but it might work for you:


    L = thisComp.layer("Text");
    xTolerance = 50;
    yTolerance = 15;

    startX = L.transform.position[0];
    startY = L.transform.position[1];
    h = thisComp.height/2;
    w = thisComp.width/2;

    rightX = curX = startX;

    blankCount = 0;
    while (blankCount < xTolerance){
    if (L.sampleImage([curX,h],[.5,h])[3] > 0){
    rightX = curX;
    blankCount = 0;
    }else{
    blankCount++;
    }
    curX++;
    }

    topY = curY = startY;

    blankCount = 0;
    while (blankCount < yTolerance){
    if (L.sampleImage([w,curY],[w,.5])[3] > 0){
    topY = curY;
    blankCount = 0;
    }else{
    blankCount++;
    }
    curY--;
    }

    myWidth = rightX - startX;
    myHeight = startY - topY;

    Dan

  • Andy Kreutzberg

    August 28, 2012 at 10:12 am

    Thanks Dan, this is really almost there. Now there is just one final problem with it. Have a look at this screenshot:

    https://img560.imageshack.us/img560/2696/screenshot20120828at115.png

    Here you can see my text setup as well as font settings etc. I have put your expression into the source text of “size_of_single_credit_block_1” and added a line to output the width and height values (“Size of text (single_credit_block_1): ” + myWidth + “x” + myHeight;). As you can see, the y parameter for height does not adjust according to the amount of lines. I have played around with the x and yTolerance values, putting in values ranging from .001 to 500 but it does not really change much about this as this second value always remains 0. I have also tried to scale the font up to enormous proportions, which sometimes gives me a small y value that seems to be unchanging though. Is there a way to fix this maybe?

    Apart from that, cool stuff! It really feels to compute a lot faster than what i had before.

  • Dan Ebberts

    August 28, 2012 at 1:06 pm

    Where’s the anchor point? The expression assumes it’s in the lower left corner of the text.

    Dan

  • Andy Kreutzberg

    August 28, 2012 at 2:16 pm

    Ha, it had to be something really simple as that. I had to move the anchor point from upper left to lower left and now it works! Really a big thanks for your help! I should be able to go on from here and hopefully this will help others trying the same thing. Maybe i post a result soon of the overall functionality in relation to the other text layers once it is done.

  • Andy Kreutzberg

    August 28, 2012 at 3:34 pm

    Just played around with it and found that it really needs to sample the layer from top down. Let me explain why this is necessary. Lines will be removed or added at the bottom of the layer only. However, if there is no written line directly above the anchor point, the expression returns zero for height even though there is still multiple lines written above it. Say, when i remove line 6 and the anchor points sits below that, it will just jump back to 0 for height. Or when there is lines added below the anchor point, the expression does not take these into account. In short, the expression only gives a correct value for the height when all lines of the text are filled up to the anchor point.

    Now i have played around and tried to fix it by inverting the values of some variables by making them negative but that does not do it. In my understanding, the only thing i need to do is inverting the direction of the sample image process on the y axis, so that when the anchor point sits at the top left corner, it analyses downwards instead of upwards, right? How does this expression recognize the sampling direction?

  • Dan Ebberts

    August 28, 2012 at 4:15 pm

    To make it go top-to-bottom, you would change
    y–
    to
    y++

    Edit: oops–that should be curY– to curY++

    and change the last line from
    myHeight = startY-topY
    to
    myHeight = topY-startY;

    For multiple lines, you’ll probably have to bump yTolerance up a lot (which will slow it down, of course).

    Dan

  • Andy Kreutzberg

    August 30, 2012 at 12:14 pm

    Again, a huge thank you, Dan! I certainly learned a lot again and just finished the first iteration of what will be the dynamic text layers. Here is the approach for everyone who wants to achieve something similar:

    1. The text layer that is to be analyzed must be duplicated, i called the duplicate single_credit_block_1_analyzer. This duplicate is hidden and the source text property is pick whipped with the source text property of the original. This analyze layer is just for the sample image to get it’s measures for the text without moving or scaling itself. If the text layer that contains dans expression would change position or scale itself based on the values from the expression, the values from Dan’s expression would be different after each change of position, leading to the text jumping around from frame to frame. So that’s the workaround for that.

    2. The analyze layer contains a point controller called ext_scanner holding dan’s expression. One line was added to transfer the values to the points: [myWidth, myHeight]; I did that for all blocks.

    3. A Null Object holds the logic for the whole comparison process of the values (here, i just did it for two blocks, but it can easily be expanded). For Position parameter of the null:

    a=thisComp.layer(“single_credit_block_1_analyzer”).effect(“ext_scanner”)(“Point”)[1];
    b=thisComp.layer(“single_credit_block_2_analyzer”).effect(“ext_scanner”)(“Point”)[1];
    x=transform.scale[1];
    y=transform.position[1];
    z=transform.position[0];

    unsortedArray=[a,b];
    sortedArray=unsortedArray.sort();
    result=sortedArray[sortedArray.length-1];

    w=linear(x,0,100,100,0)*2.4+y;

    if(result<265)
    {[z, w]};
    if(result>264)
    {[z, w-38]};

    This compares the results from Dan’s expression of each block for Height value. It also considers the scale of the blocks and adds that into the position, so when the block is automatically scaled down to fit more text in, it will also move down and stay aligned to the bottom title safe area. The If arguments come into play as soon as lines are added to the text, which will move the text upwards by 38 (size of one line in pixels).

    Now this is put into the scale parameter:

    a=thisComp.layer(“single_credit_block_1_analyzer”).effect(“ext_scanner”)(“Point”)[0];
    b=thisComp.layer(“single_credit_block_2_analyzer”).effect(“ext_scanner”)(“Point”)[0];
    y=transform.scale[1];
    z=transform.scale[0];

    unsortedArray=[a,b];
    sortedArray=unsortedArray.sort();
    result=sortedArray[0];

    if(result>511)
    {[z/result*511, y/result*511]};
    if (result<511)
    {[z, y]};

    The If argument tells the layer, that once Dan’s expression gives us more than 511 for the width of the text layer, the text will be scaled down just as much, so it can never go beyond that value.

    4. In return, the originals of the text layers (not the analyzers, as we do not want to move them) have the following for position and scale parameters:
    x=transform.position[0];
    y=thisComp.layer(“Null 2”).transform.position[1];
    [x,y];

    x=thisComp.layer(“Null 2”).transform.scale[0];
    y=thisComp.layer(“Null 2”).transform.scale[1];
    [x,y];

    I need to do some more testing with this but it already looks really good. If you have lots of text layers that all should remain aligned and positioned despite the amount of text that was put in, you could use this approach and adjust it to your needs.

  • Andy Kreutzberg

    September 20, 2012 at 1:22 pm

    Hi guys,

    you will probably declare me insane… but i kept looking for other solutions and tested, and tried, and tested…

    now it seems i have come up with a much cleaner solution that is very fast. Here is the principle:

    It is possible to break down source text to it’s individual lines. Say you have a left aligned text with 5 lines, you can single out each of these lines by using this:

    thisComp.layer("text01").text.sourceText.split("r")[0]

    This returns all the text contained in the first line of text layer “text01” and only in this line! Everything below will be ignored!
    It’s possible to use this to scale the text based on that one line length by using the following in scale property of the text layer:

    try{x=thisComp.layer("text01").text.sourceText.split("r")[0]}catch(err){x=value};

    max = 15; // characters at 100%
    n = x.length;

    if (n>15)
    {[(value[0]/n)*15,(value[1]/n)*15]}
    else
    {value}

    The last part is based on a modified code from Dan. What does it do? If the first line of text extends over 15 characters, it will be scaled by the amount of characters that exceed 15. The first part of the code contains a try catch. Because if the first line in Layer “text01” would be empty, the code would break. This way it just catches the current value.

    Now this can be extended even further to scale an entire text layer with multiple lines. Instead of putting the above code into the scale property of the text layer, create point controllers for each line and put the code there with adjustments to the [] value of x. So each point controller measures and outputs scaling information for an individual line of the text block (point 1 does line 1 at [0], point 2 does line 2 at [1], etc). Point controller “sizeline2” for line 2 for instance would contain this:

    try{x=thisComp.layer("text01").text.sourceText.split("r")[1]}catch(err){x=thisComp.layer("text01").text.sourceText.split("r")[0]};

    max = 15; // characters at 100%
    n = x.length;

    if (n>15)
    {[(value[0]/n)*15,(value[1]/n)*15]}
    else
    {value}

    The scale property of “text01” should then contain something like this:

    a1=effect("sizeline1")("Point")[0];
    b1=effect("sizeline2")("Point")[0];
    c1=effect("sizeline3")("Point")[0];
    d1=effect("sizeline4")("Point")[0];
    e1=effect("sizeline5")("Point")[0];
    f1=effect("sizeline6")("Point")[0];
    g1=effect("sizeline7")("Point")[0];
    h1=effect("sizeline8")("Point")[0];

    xF=Math.min(a1,b1,c1,d1,e1,f1);
    yF=xF;
    [xF,yF];

    It will even work with multiple text layers. Just add the point values from additional text layers as additional variables to the scale property code, so it will take into account all sizes of all lines of all text layers.

    It’s a bit complicated to understand probably but it can be worth the effort because it’s so much faster than sampleImage(). It can probably be optimized even further. I am currently trying to find a way to get the amount of lines for one text layer so you could even include the vertical size to the equation and have a real intelligent text auto scale/position solution that responds fast.

Page 1 of 2

We use anonymous cookies to give you the best experience we can.
Our Privacy policy | GDPR Policy