Okay, I got my monster!
My starting point is the last expression posted by Dan. What is slow in this expression (and in previous expressions as well) is the fact that we sample every row and column even if this row or column is contained in a larger area that does not contain non-zero alpha pixels. In other words, say your text is written at the bottom of the comp, so that the upper half of the layer has no pixels. The expression will still sample each row of this area. This looks like too much work. So if we can determine that we won't find anything in that zone, there is no need to sample there.
This observation is the base idea of my new expression which is a sort of
Divide and Conquer approach. We recursively split the layer into smaller parts and when an area is empty, we don't sample further in that area. The process is done vertically and horizontally. For instance, at the beginning, the expression determines whether there are some pixels in the upper part of the layer or not, if it finds some, then it analyzes the upper quarter, and so on until the area is around 1 pixel wide or empty.
I set up a
little project to test these two techniques and the divide and conquer expression seems to outperforms the previous one in terms of computation time, while providing a bounding box of good quality. When I rendered the two comps in the project, the "new" comp was 375% faster than the "old" comp.
In both methods, I noticed that when the text is very small compared to the size of the comp, when we sample a row or a column that contains very few pixels, sampleImage() round the result to zero, so we may miss some pixel.
The expression isn't easily readable but if someone wants to improve it, here it is:
Code: Select all
function getParams(area)
{
var pointX = (area[0][0] + area[1][0]) / 2;
var pointY = (area[0][1] + area[1][1]) / 2;
var radiusX = (area[1][0] - area[0][0]) / 2;
var radiusY = (area[1][1] - area[0][1]) / 2;
return [ [pointX,pointY], [radiusX,radiusY] ];
}
function isAreaEmpty(area)
{
var params = getParams(area);
if (params[1][0] < 0.5 || params[1][1] < 0.5)
return true;
return L.sampleImage(params[0], params[1], true)[3] > 0 ? false : true;
}
function sampleRow(area)
{
var params = getParams(area);
if (L.sampleImage(params[0], params[1], true)[3] > 0)
{
if (params[0][1] < top)
top = params[0][1];
if (params[0][1] > bottom)
bottom = params[0][1];
}
}
function sampleCol(area)
{
var params = getParams(area);
if (L.sampleImage(params[0], params[1], true)[3] > 0)
{
if (params[0][0] < left)
left = params[0][0];
if (params[0][0] > right)
right = params[0][0];
}
}
function sampleAreaHorizontally(area)
{
if (isAreaEmpty(area))
return;
if (area[1][1] - area[0][1] < 1.5)
sampleRow(area);
var UpperArea = [ area[0], [area[1][0],(area[0][1]+area[1][1])/2] ];
var LowerArea = [ [area[0][0],(area[0][1]+area[1][1])/2], area[1] ];
sampleAreaHorizontally(UpperArea);
sampleAreaHorizontally(LowerArea);
}
function sampleAreaVertically(area)
{
if (isAreaEmpty(area))
return;
if (area[1][0] - area[0][0] < 1.5)
sampleCol(area);
var LeftArea = [ area[0], [(area[0][0]+area[1][0])/2, area[1][1]] ];
var RightArea = [ [(area[0][0]+area[1][0])/2, area[0][1]], area[1] ];
sampleAreaVertically(LeftArea);
sampleAreaVertically(RightArea);
}
var L = thisComp.layer("Text");
var top = L.height, bottom = 0, left = L.width, right = 0;
var halfW = L.width / 2;
var halfH = L.height / 2;
var area = [[0,0], [L.width,L.height]];
sampleAreaHorizontally(area);
sampleAreaVertically(area);
"" + left + "," + top + "," + right + "," + bottom;
I know this looks like overkill but we are in the "Discuss the code" section
